监听列表导致FutureProvider无限触发的问题解决
解决Riverpod搭配Isar时FutureProvider无限通知导致Widget重复Build的问题
你的推测没错:每次databaseManager.spools返回的List<String>都是新实例,即使内容完全一致,Riverpod基于对象引用的相等性判断会认为值已变化,从而反复通知监听者,导致Widget重复Build。下面是两种可行的解决方案:
方案一:改用StreamProvider监听Isar实时查询流
Isar支持通过watch()方法将查询转为Stream,只有当数据库中数据真的发生变化时,才会发射新的列表。这种方式能从根源避免不必要的更新。
修改Provider代码:
@riverpod Stream<List<String>> spools(SpoolsRef ref) async* { final databaseManager = await ref.watch(databaseManagerProvider.future); // 替换为你实际的Isar查询watch逻辑,fireImmediately确保初始数据能立即发射 yield* databaseManager.isar.spools.watch(fireImmediately: true); }
修改Consumer代码:
/// Get all spools and make [DropdownMenuEntries] from them. List<DropdownMenuEntry> spoolsMenuEntries() { return ref.watch(spoolsProvider).when( data: (spools) => spools .map((spool) => DropdownMenuEntry(value: spool, label: spool)) .toList(), loading: () => [ const DropdownMenuEntry( value: defaultSpoolName, label: defaultSpoolName, ) ], error: (_, __) => [ const DropdownMenuEntry( value: defaultSpoolName, label: defaultSpoolName, ) ], ); }
方案二:在FutureProvider中添加列表内容相等性判断
如果坚持使用FutureProvider,可以通过对比列表内容,仅当内容真的变化时才返回新值,避免无意义的更新。
修改Provider代码:
@riverpod Future<List<String>> spools(SpoolsRef ref) async { final databaseManager = await ref.watch(databaseManagerProvider.future); final newSpools = await databaseManager.spools; // 对比当前缓存的列表与新列表的内容 final currentState = ref.state; if (currentState is AsyncData<List<String>>) { final oldSpools = currentState.value; if (_listsContentEqual(oldSpools, newSpools)) { // 内容一致,返回旧列表引用,避免触发更新 return oldSpools; } } return newSpools; } // 辅助函数:判断两个字符串列表内容是否完全一致 bool _listsContentEqual(List<String>? a, List<String>? b) { if (a == null && b == null) return true; if (a == null || b == null) return false; if (a.length != b.length) return false; for (int i = 0; i < a.length; i++) { if (a[i] != b[i]) return false; } return true; }
这种方式下,即使查询返回新的列表实例,只要内容和之前一致,Provider会复用旧的列表引用,Riverpod会判定值未变化,不会通知监听者。
内容的提问来源于stack exchange,提问作者Microbob
相关产品推荐
相关产品推荐

