Flutter Provider: How to notify a model that a change happened on a model it contains?
this ist not a nested Provider, but i think in your example it is the better way..
only one ChangeNotifierProvider per section ("Frozen", "Fruits and Veggies")
is defined
the complete()
function from a ItemModel
is in the GroceryListSectionModel()
and with the parameter from the current List Index
class GroceryListSection extends StatelessWidget {
final GroceryListSectionModel model;
// final ValueChanged<bool> onChanged;
GroceryListSection(this.model);
@override
Widget build(BuildContext context) {
return new ChangeNotifierProvider<GroceryListSectionModel>(
create: (context) => GroceryListSectionModel(model.name, model.items),
child: new Consumer<GroceryListSectionModel>(
builder: (context, groceryListSection, child) {
return Container(
child: ExpansionTile(
title: Text(model.name),
subtitle: Text("${groceryListSection.completedItemCount()} of ${groceryListSection.itemCount()} completed"),
children: groceryListSection.items.asMap().map((i, groceryItemModel) => MapEntry(i, GroceryItem(groceryItemModel, i))).values.toList()
)
);
}
)
);
}
}
class GroceryItem extends StatelessWidget {
final GroceryItemModel model;
final int index;
GroceryItem(this.model, this.index);
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(model.name),
leading: model.completed ? Icon(Icons.check_circle, color: Colors.green) : Icon(Icons.radio_button_unchecked),
onTap: () => Provider.of<GroceryListSectionModel>(context, listen: false).complete(index),
);
}
}
class GroceryListSectionModel extends ChangeNotifier {
String name;
List<GroceryItemModel> items;
GroceryListSectionModel(this.name, [items]) {
this.items = items == null ? [] : items;
}
int itemCount() => items.length;
int completedItemCount() => items.where((item) => item.completed).length;
// complete Void with index from List items
void complete(int index) {
this.items[index].completed = true;
notifyListeners();
}
}
// normal Model without ChangeNotifier
class GroceryItemModel {
final String name;
bool completed = false;
GroceryItemModel({this.name, completed}) {
this.completed = completed == null ? false : completed;
}
}
DogsGoQuack
Updated on December 12, 2022Comments
-
DogsGoQuack over 1 year
I'm starting to learn Flutter/Dart by building a simple Todo app using Provider, and I've run into a state management issue. To be clear, the code I've written works, but it seems... wrong. I can't find any examples that resemble my case enough for me to understand what the correct way to approach the issue is.
This is what the app looks like
It's a grocery list divided by sections ("Frozen", "Fruits and Veggies"). Every section has multiple items, and displays a "x of y completed" progress indicator. Every item "completes" when it is pressed.
The
GroceryItemModel
looks like this:class GroceryItemModel extends ChangeNotifier { final String name; bool _completed = false; GroceryItemModel(this.name); bool get completed => _completed; void complete() { _completed = true; notifyListeners(); } }
And I use it in the
GroceryItem
widget like so:class GroceryItem extends StatelessWidget { final GroceryItemModel model; GroceryItem(this.model); @override Widget build(BuildContext context) { return ChangeNotifierProvider.value( value: model, child: Consumer<GroceryItemModel>(builder: (context, groceryItem, child) { return ListTile( title: Text(groceryItem.name), leading: groceryItem.completed ? Icon(Icons.check_circle, color: Colors.green) : Icon(Icons.radio_button_unchecked) onTap: () => groceryItem.complete(); }) ); } }
The next step I want is to include multiple items in a section, which tracks completeness based on how many items are completed.
The
GroceryListSectionModel
looks like this:class GroceryListSectionModel extends ChangeNotifier { final String name; List<GroceryItemModel> items; GroceryListSectionModel(this.name, [items]) { this.items = items == null ? [] : items; // THIS RIGHT HERE IS WHERE IT GETS WEIRD items.forEach((item) { item.addListener(notifyListeners); }); // END WEIRD } int itemCount() => items.length; int completedItemCount() => items.where((item) => item.completed).length; }
And I use it in the
GroceryListSection
widget like so:class GroceryListSection extends StatelessWidget { final GroceryListSectionModel model; final ValueChanged<bool> onChanged; GroceryListSection(this.model, this.onChanged); @override Widget build(BuildContext context) { return ChangeNotifierProvider.value( value: model, child: Consumer<GroceryListSectionModel>( builder: (context, groceryListSection, child) { return Container( child: ExpansionTile( title: Text(model.name), subtitle: Text("${groceryListSection.completedItemCount()} of ${groceryListSection.itemCount()} completed"), children: groceryListSection.items.map((groceryItemModel) => GroceryItem(groceryItemModel)).toList() ) ); } ) ); } }
The Problems:
- It seems weird to have a
ChangeNotifierProvider
and aConsumer
in both Widgets. None of the examples I've seen do that. - It's definitely wrong to have the
GroceryListSectionModel
listening to changes on all theGroceryItemModels
for changes to propagate back up the tree. I don't see how that can scale right.
Any suggestions? Thanks!
- It seems weird to have a