Поставщик Flutter: Как уведомить модель о том, что произошло изменение в модели, которую она содержит? - PullRequest
1 голос
/ 19 апреля 2020

Я начинаю изучать Flutter / Dart, создав простое приложение Todo с использованием Provider, и столкнулся с проблемой управления состоянием. Чтобы было ясно, код, который я написал, работает, но кажется ... неправильным. Я не могу найти примеров, которые бы напоминали мой случай, чтобы я мог понять, как правильно подойти к проблеме.

Так выглядит приложение

Это список покупок, разделенный на разделы («Замороженные», «Фрукты и овощи»). Каждый раздел имеет несколько элементов и отображает индикатор хода выполнения "x of y complete". Каждый элемент «завершается» при нажатии.

GroceryItemModel выглядит следующим образом:

class GroceryItemModel extends ChangeNotifier {
  final String name;

  bool _completed = false;

  GroceryItemModel(this.name);

  bool get completed => _completed;

  void complete() {
    _completed = true;
    notifyListeners();
  }
}

И я использую его в виджете GroceryItem как Итак:

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();
        })
    );
  }
}

Следующий шаг, который я хочу, - включить несколько элементов в секцию , которая отслеживает полноту на основе количества выполненных элементов.

GroceryListSectionModel выглядит так:

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;
}

И я использую его в виджете GroceryListSection примерно так:

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()
              )
          );
        }
      )
    );
  }
}

Проблемы:

  1. Кажется странным иметь ChangeNotifierProvider и Consumer в обоих виджетах. Ни один из примеров, которые я видел, не делает этого.
  2. Это определенно неправильно, если GroceryListSectionModel слушает изменения на всех GroceryItemModels, чтобы изменения распространялись обратно вверх по дереву. Я не вижу, как это может масштабироваться правильно.

Есть предложения? Спасибо!

1 Ответ

1 голос
/ 19 апреля 2020

это не вложенный поставщик, но я думаю, что в вашем примере это лучший способ.

определен только один ChangeNotifierProvider на section ("Frozen", "Fruits and Veggies")

функция complete() из ItemModel находится в GroceryListSectionModel() и с параметром из текущего индекса списка

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;
  }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...