【问题标题】:How best to use Riverpod to manage items in a list如何最好地使用 Riverpod 管理列表中的项目
【发布时间】:2021-09-23 14:17:06
【问题描述】:

我正在努力弄清楚如何在以下场景中使用 Riverpod。

我有一个 ListView,其中的孩子是容器,里面有一个按钮。

按下按钮时,我想更改该容器的颜色。我希望使用 Riverpod 提供程序将该颜色存储在一个状态中。

列表之外还有另一个按钮。当按下它时,它应该改变所有容器的颜色。

感觉每个容器都需要一个 Change/StateNotifierProvider。我为此使用家庭吗?我如何将特定的状态绑定到其关联的容器?

红色按钮如何访问所有状态以更改所有状态的颜色?

作为奖励,我还希望在其中一个绿色按钮更改其容器颜色时通知红色按钮

非常感谢

【问题讨论】:

  • 有固定数量的容器吗?
  • 否 - 未修复,可能有 100 秒
  • 列表是否保证保持相同的顺序?
  • 不怕!容器列表实际上是从数据库中的一组行构建的,并且没有可用的唯一 Id ......虽然,再想一想,我想我可以推导出一个唯一的 Id
  • 派生唯一的 ID 将是要走的路。我会尽快发布答案。

标签: flutter riverpod


【解决方案1】:

您可以使用家庭,但在这种情况下,由于您的条目数量不固定,这会使事情变得不必要地复杂化。

这是一个使用hooks_riverpod 编写的完整可运行示例。如果您需要我翻译为不使用钩子,我也可以这样做。请记住,这是故意简单且有点幼稚,但应该适应您的情况。

首先,一个模型类。我通常会使用freezed,但这超出了这个问题的范围。

class Model {
  final int id;
  final Color color;

  Model(this.id, this.color);
}

接下来,StateNotifier:

class ContainerListState extends StateNotifier<List<Model>> {
  ContainerListState() : super(const []);

  static final provider = StateNotifierProvider<ContainerListState, List<Model>>((ref) {
    return ContainerListState();
  });

  void setAllColor(Color color) {
    state = state.map((model) => Model(model.id, color)).toList();
  }

  void setModelColor(Model model, Color color) {
    final id = model.id;
    state = state.map((model) {
      return model.id == id ? Model(id, color) : model;
    }).toList();
  }

  void addItem() {
    // TODO: Replace state.length with your unique ID
    state = [...state, Model(state.length, Colors.lightBlue)];
  }
}

最后,UI 组件(钩子):

class MyHomePage extends HookWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final modelList = useProvider(ContainerListState.provider);
    return Scaffold(
      appBar: AppBar(
        title: Text('ListView of Containers'),
        actions: [
          IconButton(
            icon: Icon(Icons.add),
            onPressed: () {
              context.read(ContainerListState.provider.notifier).addItem();
            },
          ),
        ],
      ),
      body: ListView.builder(
        itemCount: modelList.length,
        itemBuilder: (_, index) {
          return ContainerWithButton(model: modelList[index]);
        },
      ),
      floatingActionButton: RedButton(),
    );
  }
}

class ContainerWithButton extends StatelessWidget {
  const ContainerWithButton({
    Key? key,
    required this.model,
  }) : super(key: key);

  final Model model;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      tileColor: model.color,
      trailing: ElevatedButton(
        style: ElevatedButton.styleFrom(primary: Colors.lightGreen),
        onPressed: () {
          context.read(ContainerListState.provider.notifier).setModelColor(model, Colors.purple);
        },
        child: Text('Button'),
      ),
    );
  }
}

class RedButton extends HookWidget {
  const RedButton({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // Bonus: Red button will be notified on changes
    final state = useProvider(ContainerListState.provider);

    return FloatingActionButton.extended(
      onPressed: () {
        context.read(ContainerListState.provider.notifier).setAllColor(Colors.orange);
      },
      backgroundColor: Colors.red,
      label: Text('Set all color'),
    );
  }
}

非钩子:

class MyHomePage extends ConsumerWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final modelList = watch(ContainerListState.provider);
    return Scaffold(
      appBar: AppBar(
        title: Text('ListView of Containers'),
        actions: [
          IconButton(
            icon: Icon(Icons.add),
            onPressed: () {
              context.read(ContainerListState.provider.notifier).addItem();
            },
          ),
        ],
      ),
      body: ListView.builder(
        itemCount: modelList.length,
        itemBuilder: (_, index) {
          return ContainerWithButton(model: modelList[index]);
        },
      ),
      floatingActionButton: RedButton(),
    );
  }
}

class ContainerWithButton extends StatelessWidget {
  const ContainerWithButton({
    Key? key,
    required this.model,
  }) : super(key: key);

  final Model model;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      tileColor: model.color,
      trailing: ElevatedButton(
        style: ElevatedButton.styleFrom(primary: Colors.lightGreen),
        onPressed: () {
          context.read(ContainerListState.provider.notifier).setModelColor(model, Colors.purple);
        },
        child: Text('Button'),
      ),
    );
  }
}

class RedButton extends ConsumerWidget {
  const RedButton({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, ScopedReader watch) {
    // Bonus: Red button will be notified on changes
    final state = watch(ContainerListState.provider);

    return FloatingActionButton.extended(
      onPressed: () {
        context.read(ContainerListState.provider.notifier).setAllColor(Colors.orange);
      },
      backgroundColor: Colors.red,
      label: Text('Set all color'),
    );
  }
}

我建议将其放入新的 Flutter 应用中进行测试。

【讨论】:

  • 感谢一百万亚历克斯,这对我来说是一个很好的教育。我仍在尝试了解更详细的细节,尤其是因为我之前没有使用过 hooks_riverpod(所以如果你有时间展示一个非 hooks 版本,那也会非常有帮助)。我看到您正在管理整个列表的状态(ContainerListState),而不是为每个容器拥有一个单独的状态。据推测,这意味着当在 ONE 容器上点击按钮时,将重建整个 LIST 状态。直觉上,这感觉效率低下(可能会导致不必要的小部件重建?)还是我什么都不担心?
  • addItem()中使用state = [...state, xxxx]的原因:stackoverflow.com/a/65380308/10927806
  • @DaveBound 你是对的,这会导致整个列表重建。我不会说这是最有效的方法,但它应该是最简单的方法。我不会为此使用家庭,因为您必须跟踪您创建的每个家庭。
  • 在我添加非钩子版本之前,请问您使用的是riverpod 0.14还是1.0
  • flutter_riverpod: ^0.12.1
猜你喜欢
  • 1970-01-01
  • 2021-04-01
  • 2012-08-07
  • 1970-01-01
  • 2015-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-08
相关资源
最近更新 更多