Riverpod如何维护列表选中项及索引并同步列表变更?
现有实现问题分析
你的实现已经跑通了核心逻辑,但存在几个可优化的问题:
- 耦合度过高:
NameListNotifier中硬编码调用固定序号的selectionProvider更新方法,后续如果新增更多选中状态,必须修改列表类的代码,扩展性极差 - 空列表判断逻辑错误:Dart 中列表的
==比较的是内存引用,names == []永远不会返回 true,应该使用names.isEmpty判断 - 手动触发更新不可靠:所有列表修改的入口都要手动调用选中状态的更新方法,一旦漏写就会出现状态不同步的问题
- 没有用到 Riverpod 自带的监听能力,属于手动实现了状态依赖,不符合框架的最佳实践
优化方案
我们可以利用 Riverpod 的内置监听能力,让每个选中状态自动监听列表的变化,完全解耦列表和选中逻辑:
import 'package:riverpod/riverpod.dart'; void main(List<String> arguments) { final container = ProviderContainer(); List<String> names = ["Jack", "Adam", "Sally"]; container.read(nameListProvider.notifier).setNames(names); final firstSelection = container.read(selectionProvider(1).notifier); firstSelection.setByIndex(1); print('第一个选中项:${container.read(selectionProvider(1)).name},索引:${container.read(selectionProvider(1)).index}'); final secondSelection = container.read(selectionProvider(2).notifier); secondSelection.setByIndex(2); print('第二个选中项:${container.read(selectionProvider(2)).name},索引:${container.read(selectionProvider(2)).index}'); // 模拟列表变更 names.remove('Adam'); container.read(nameListProvider.notifier).setNames(List.from(names)); print('列表更新后第一个选中项:${container.read(selectionProvider(1)).name},索引:${container.read(selectionProvider(1)).index}'); print('列表更新后第二个选中项:${container.read(selectionProvider(2)).name},索引:${container.read(selectionProvider(2)).index}'); } // 选中状态数据类,新增copyWith方便更新 class Selection { final String? name; final int? index; Selection({this.name, this.index}); Selection copyWith({ String? Function()? name, int? Function()? index, }) { return Selection( name: name != null ? name() : this.name, index: index != null ? index() : this.index, ); } } // 选中状态Provider,用family区分不同的选中实例 final selectionProvider = StateNotifierProvider.family<SelectionNotifier, Selection, int>((ref, id) { return SelectionNotifier(ref); }); class SelectionNotifier extends StateNotifier<Selection> { final Ref _ref; SelectionNotifier(this._ref) : super(Selection()) { // 核心:自动监听列表变化,列表更新时自动同步选中状态 _ref.listen<List<String>>(nameListProvider, (previous, next) { _syncSelection(); }); } // 同步选中状态逻辑 void _syncSelection() { final nameList = _ref.read(nameListProvider); // 列表为空直接重置 if (nameList.isEmpty) { state = Selection(); return; } // 没有选中记录默认选第一个 final currentName = state.name; if (currentName == null) { state = Selection(name: nameList[0], index: 0); return; } // 查找当前选中名称的新索引 final newIndex = nameList.indexOf(currentName); if (newIndex == -1) { // 已删除选第一个 state = Selection(name: nameList[0], index: 0); return; } // 存在就更新索引 state = state.copyWith(index: () => newIndex); } // 主动选中指定索引的元素 void setByIndex(int index) { final nameList = _ref.read(nameListProvider); if (nameList.isEmpty || index < 0 || index >= nameList.length) return; state = Selection(name: nameList[index], index: index); } } // 列表状态Provider,不需要关心有多少个选中状态 final nameListProvider = StateNotifierProvider<NameListNotifier, List<String>>((ref) { return NameListNotifier(); }); class NameListNotifier extends StateNotifier<List<String>> { NameListNotifier() : super([]); void setNames(List<String> names) { state = List.unmodifiable(names); // 用不可变列表避免外部意外修改 } }
优化点说明
- 完全解耦:列表状态和选中状态互不感知,新增选中状态只需要直接调用
selectionProvider(新id)即可,不需要修改任何现有代码 - 自动同步:依赖 Riverpod 的监听能力,列表变更后所有选中状态自动更新,不会出现漏更的问题
- 鲁棒性提升:修复了空列表判断错误,新增了索引越界校验,列表使用不可变对象避免外部误改
- 符合 Riverpod 最佳实践:使用 Ref 而非 Reader 存储引用,支持完整的框架能力
内容的提问来源于stack exchange,提问作者Jonah Kornberg
相关产品推荐
相关产品推荐

