14

I want to update my ListView if i remove or add items. Right now i just want to delete items and see the deletion of the items immediately.

My application is more complex so i wrote a small example project to show my problems.

The TestItem class holds some data entries:

class TestItem {
  static int id = 1;
  bool isFinished = false;
  String text;
  TestItem() {
    text = "Item ${id++}";
  }
}

The ItemInfoViewWidget is the UI representation of the TestItem and removes the item if it is finished (whenever the Checkbox is changed to true).

class ItemInfoViewWidget extends StatefulWidget {
  TestItem item;
  List<TestItem> items;

  ItemInfoViewWidget(this.items, this.item);

  @override
  _ItemInfoViewWidgetState createState() =>
      _ItemInfoViewWidgetState(this.items, this.item);
}

class _ItemInfoViewWidgetState extends State<ItemInfoViewWidget> {
  TestItem item;
  List<TestItem> items;

  _ItemInfoViewWidgetState(this.items, this.item);

  @override
  Widget build(BuildContext context) {
    return new Card(
      child: new Column(
        children: <Widget>[
          new Text(this.item.text),
          new Checkbox(
              value: this.item.isFinished, onChanged: isFinishedChanged)
        ],
      ),
    );
  }

  void isFinishedChanged(bool value) {
    setState(() {
      this.item.isFinished = value;
      this.items.remove(this.item);
    });
  }
}

The ItemViewWidget class builds the ListView.

class ItemViewWidget extends StatefulWidget {
  List<TestItem> items;

  ItemViewWidget(this.items);

  @override
  _ItemViewWidgetState createState() => _ItemViewWidgetState(this.items);
}

class _ItemViewWidgetState extends State<ItemViewWidget> {
  List<TestItem> items;

  _ItemViewWidgetState(this.items);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: new Text('Test'),
      ),
      body: ListView.builder(
          itemCount: this.items.length,
          itemBuilder: (BuildContext context, int index) {
            return new ItemInfoViewWidget(this.items, this.items[index]);
          }),
    );
  }
}

The MyApp shows one TestItem and a button that navigates to the ItemViewWidget page.

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  List<TestItem> items = new List<TestItem>();

  _MyHomePageState() {
    for (int i = 0; i < 20; i++) {
      this.items.add(new TestItem());
    }
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text(widget.title),
        ),
        body: new Column(
          children: <Widget>[
            ItemInfoViewWidget(this.items, this.items.first),
            FlatButton(
              child: new Text('Open Detailed View'),
              onPressed: buttonClicked,
            )
          ],
        ));
  }

  void buttonClicked() {
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => ItemViewWidget(this.items)),
    );
  }
}

If i toggle the Checkbox of the first item, the Checkbox is marked as finished (as expected), but it is not removed from the UI - however it is removed from the list.

Then I go back to the Main page and I can observe that Item 1 is checked there as well.

So if I go to the ItemViewWidget page again, I can observe that the checked items are no longer present. Based on these observations, I come to the conclusion that my implementation works, but my UI is not updating.

How can I change my code to make an immediate update of the UI possible?

Edit: This is not a duplicate, because

  1. I dont want to create a new instance of my list just to get the UI updated.
  2. The answer does not work: I added this.items = List.from(this.items); but the behavior of my app is the same as already described above.
  3. I don't want to break my reference chain by calling List.from, because my architecture has one list that is referenced by several classes. If i break the chain i have to update all references by my own. Is there a problem with my architecture?
7
  • 1
    Possible duplicate of ListView does not refresh whereas attached list does (Flutter) Commented Aug 20, 2018 at 12:56
  • @RémiRousselet I followed the answer but i got no difference to my solution. The item is removed but is not updated in the UI. I also dont want to create a new List because then my List in the ItemViewWidget is 'disconnected' from the List of MyApp. It could also be that my architecture is bad and there is a better way of accomplishing what I want to do? Commented Aug 20, 2018 at 13:30
  • Your list instance stays the same. You shouldn't call list.remove. Commented Aug 20, 2018 at 13:31
  • Why should't i call list.remove? I looked into my code with the debugger and i compared the hash code of the instance before and after List.from. Before it was 623876470 and after it was 812698102. I guess List.from creates a new instance (or the hashcode is not the right metric to track an instance). Commented Aug 20, 2018 at 13:52
  • That's the idea. You should deliberately create a new instance, hence the List.from Commented Aug 20, 2018 at 13:55

1 Answer 1

32

I dont want to create a new instance of my list just to get the UI updated.

Flutter uses immutable object. Not following this rule is going against the reactive framework. It is a voluntary requirement to reduce bugs.

Fact is, this immutability is here especially to prevents developers from doing what you currently do: Having a program that depends on sharing the same instance of an object between classes; as multiple classes may want to modify it.


The real problem lies in the fact that it is your list item that removes delete an element from your list.

The thing is since it's your item which does the computing, the parent is never notified that the list changed. Therefore it doesn't know it should rerender. So nothing visually change.

To fix that you should move the deletion logic to the parent. And make sure that the parent correctly calls setState accordingly. This would translate into passing a callback to your list item, which will be called on deletion.

Here's an example:

class MyList extends StatefulWidget {
  @override
  _MyListState createState() => _MyListState();
}

class _MyListState extends State<MyList> {
  List<String> list = List.generate(100, (i) => i.toString());

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: list.length,
      itemBuilder: (context, index) {
        return MyItem(list[index], onDelete: () => removeItem(index));
      },
    );
  }

  void removeItem(int index) {
    setState(() {
      list = List.from(list)
        ..removeAt(index);
    });
  }
}

class MyItem extends StatelessWidget {
  final String title;
  final VoidCallback onDelete;

  MyItem(this.title, {this.onDelete});

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(this.title),
      onTap: this.onDelete,
    );
  }
}
Sign up to request clarification or add additional context in comments.

3 Comments

So i evaluated your solution and it is working as it is. However, I modified your StatelessWidget to a StatefulWidget and the solution is no longer working. I just added a constructor with the two parameters title and onDelete and passed these to the State, too. Any clue why this is? If you need my code i can edit my answer.
I've seen in your ItemInfoViewWidget that you actually pass parameters to the State constructor. Don't. See stackoverflow.com/questions/50818770/… this might be your problem
That was the problem.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.