5

I have this problem: I have a list of Items, each with a favorite status, and I have a page that shows the favorite items. I want the page to update and remove the non-favorite items when I toggle the favorite status.

Tried with ChangeNotifierProxyProvider also with no success, the status is changing but the notifyListeners does not update the main list. I know the trick with an empty setState in the middle of the app just to trigger the refresh but I know there is a more neat method involving Provider that I don't know of.

This is the code (I made it as short as possible)

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => Collection(),
      builder: (context, child) => MaterialApp(
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final _list = Provider.of<Collection>(context).favItems;

    return Scaffold(
      appBar: AppBar(
        title: Text("Test"),
      ),
      body: Container(
        child: ListView.builder(
          itemCount: _list.length,
          itemBuilder: (_, index) => ChangeNotifierProvider.value(
            value: _list[index],
            child: CardWidget(),
          ),
        ),
      ),
    );
  }
}

class CardWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final _item = Provider.of<Items>(context);

    return Card(
      child: Row(
        children: <Widget>[
          Text(_item.name),
          IconButton(
              icon:
                  _item.favourite ? Icon(Icons.star) : Icon(Icons.star_border),
              onPressed: () {
                _item.toggleFavourite();
              })
        ],
      ),
    );
  }
}

class Collection with ChangeNotifier {
  List<Items> _items = [
    Items(name: "a", favourite: true),
    Items(name: "b", favourite: true),
    Items(name: "c", favourite: true),
  ];

  List<Items> get favItems =>
      [..._items].where((element) => element.favourite == true).toList();
}

class Items with ChangeNotifier {
  String name;
  bool favourite;

  Items({this.name, this.favourite});

  toggleFavourite() {
    favourite = !favourite;
    notifyListeners();
  }
}
Randomize
  • 8,651
  • 18
  • 78
  • 133
Andrei
  • 53
  • 1
  • 1
  • 4

3 Answers3

7

To be honest the way Provider works is by propagating the data down the widget tree, from parent to children, but you want the child (Item) to notify the parent to change (Collection) and that would make both parent and children codependent (and would lead to some spaguetti code later). If every change to the favourite is going to update the list then it would be better just to keep the whole logic in Collection and keep the Item class as a simpel model

class Collection with ChangeNotifier {
  List<Items> _items = [
    Items(name: "a", favourite: true),
    Items(name: "b", favourite: true),
    Items(name: "c", favourite: true),
  ];

  List<Items> get favItems =>
      [..._items].where((element) => element.favourite == true).toList();

  toggleFavourite(Item item) {
    int index = _items.indexWhere((element) => element.name == item.name);
    _items[index].favourite = !_items[index].favourite;
    notifyListeners();
  }
}

class Items { //simple Model class
  String name;
  bool favourite;

  Items({this.name, this.favourite});
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final _list = context.select((Collection list) => list.favItems); 
    //to update only if list is different

    return Scaffold(
      appBar: AppBar(
        title: Text("Test"),
      ),
      body: Container(
        child: ListView.builder(
          itemCount: _list.length,
          itemBuilder: (_, index) => CardWidget(_list[index])
        ),
      ),
    );
  }
}

class CardWidget extends StatelessWidget {
  final Item _item;

  CardWidget(this._item);

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Row(
        children: <Widget>[
          Text(_item.name),
          IconButton(
              icon:
                  _item.favourite ? Icon(Icons.star) : Icon(Icons.star_border),
              onPressed: () {
                Provider.of<Collection>(context, listen: false).toggleFavourite(_item);
              })
        ],
      ),
    );
  }
}
EdwynZN
  • 4,895
  • 2
  • 12
  • 15
0

Is the problem that the list isn't visually updating altough the item is removed from the list? Try using:

Container(
    key: UniqueKey(),
    child: CardWidget()
)

I don't know why it works but this worked for me when I had this problem.

Quasi
  • 576
  • 4
  • 13
  • It doesn't work. The list itself is updating, if I do a hot reload it shows changes. It doesn't rebuild when I call the notifyListeners. – Andrei Jul 12 '20 at 20:07
-2

Try use the 'setState()' method, that rebuild your screen and You should see the changes

Eduardo Andres
  • 192
  • 1
  • 8
  • 1
    This question is more than updating the current screen, if the value has been modified in the other page and you go back to previous page, setState is not going to help you out – Afzal Ali May 26 '21 at 16:12