如何用Riverpod实现列表实时过滤?TextField异常问题求助
问题描述
我想用TextField实现列表的实时过滤更新,但一直没成功。尝试将TextField对应的searchTextProvider和管理列表的AsyncNotifier组合后,遇到两个问题:
- 不使用
Timer? _debounceTimer时,输入单个字符UI会即时更新,但TextField会自行清空; - 使用
Timer? _debounceTimer可以避免频繁更新,但没法实现真正的实时更新,而且TextField还是会清空。
现有Providers代码
final searchTextProvider = StateProvider<String>((ref) => ''); class ProductScreenNotifier extends AsyncNotifier<List<Product>> { Timer? _debounceTimer; Future<List<Product>> _fetchProducts() async { final filter = ref.watch(searchTextProvider); final allProducts = await ProductRepository.instance.getAll(); if (filter.isNotEmpty) { return allProducts .where((product) => product.name.toLowerCase().contains(filter.toLowerCase())) .toList(); } else { return allProducts; } } @override Future<List<Product>> build() async { return _fetchProducts(); } Future<void> addProduct(Product product) async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { await ProductRepository.instance.insert(product); return _fetchProducts(); }); } Future<void> deleteProduct(int id) async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { await ProductRepository.instance.delete(id); return _fetchProducts(); }); } void updateSearchText(String value) { _debounceTimer?.cancel(); _debounceTimer = Timer(const Duration(milliseconds: 500), () { ref.read(searchTextProvider.notifier).update((_) => value); _fetchProducts(); }); } } final asyncProductScreenProvider = AsyncNotifierProvider<ProductScreenNotifier, List<Product>>(() { return ProductScreenNotifier(); });
现有TextField代码
Widget searchProductField(WidgetRef ref) { return TextField( onChanged: (value) { ref.read(asyncProductScreenProvider.notifier).updateSearchText(value); }, decoration: const InputDecoration(labelText: "Pesquisar Produtos"), ); }
解决方案
核心问题拆解
- TextField清空问题:输入框未绑定
searchTextProvider的状态值,每次UI因列表更新重建时,输入框会回到初始空值; - 实时更新逻辑冗余:无需手动调用
_fetchProducts(),AsyncNotifier的build方法可通过监听searchTextProvider自动触发数据刷新。
代码修正步骤
1. 绑定TextField与搜索状态,解决清空问题
给TextField绑定searchTextProvider的当前值,确保输入内容与状态同步:
Widget searchProductField(WidgetRef ref) { final searchText = ref.watch(searchTextProvider); // 使用controller绑定状态,避免UI重建时清空输入框 final controller = TextEditingController(text: searchText) ..selection = TextSelection.fromPosition( TextPosition(offset: searchText.length), ); return TextField( controller: controller, onChanged: (value) { // 直接更新搜索状态,无需通过AsyncNotifier中转 ref.read(searchTextProvider.notifier).state = value; }, decoration: const InputDecoration(labelText: "搜索产品"), ); }
2. 简化AsyncNotifier,实现自动实时更新
移除冗余的Timer和手动调用逻辑,让AsyncNotifier自动监听搜索文本变化:
final searchTextProvider = StateProvider<String>((ref) => ''); class ProductScreenNotifier extends AsyncNotifier<List<Product>> { Future<List<Product>> _fetchProducts() async { final filter = ref.watch(searchTextProvider); final allProducts = await ProductRepository.instance.getAll(); if (filter.isNotEmpty) { return allProducts .where((product) => product.name.toLowerCase().contains(filter.toLowerCase())) .toList(); } else { return allProducts; } } @override Future<List<Product>> build() async { // 监听searchTextProvider,文本变化时自动重新执行build ref.watch(searchTextProvider); return _fetchProducts(); } Future<void> addProduct(Product product) async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { await ProductRepository.instance.insert(product); return _fetchProducts(); }); } Future<void> deleteProduct(int id) async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { await ProductRepository.instance.delete(id); return _fetchProducts(); }); } } final asyncProductScreenProvider = AsyncNotifierProvider<ProductScreenNotifier, List<Product>>(() { return ProductScreenNotifier(); });
3. 可选:添加防抖优化(减少请求频率)
如果担心搜索文本频繁变化导致多次请求,可以在状态层单独做防抖:
final searchTextProvider = StateProvider<String>((ref) => ''); // 防抖后的搜索文本Provider final debouncedSearchTextProvider = Provider<String>((ref) { final searchText = ref.watch(searchTextProvider); // 定义防抖时长 const debounceDuration = Duration(milliseconds: 500); // 用Timer实现防抖逻辑 final timer = Timer(debounceDuration, () {}); ref.onDispose(() => timer.cancel()); return ref.watch( FutureProvider<String>((ref) async { await Future.delayed(debounceDuration); return searchText; }).future, ) ?? searchText; });
之后在_fetchProducts中替换为ref.watch(debouncedSearchTextProvider)即可,兼顾实时输入体验与请求频率控制。
关键说明
AsyncNotifier的build方法中通过ref.watch(searchTextProvider)监听状态,搜索文本变化时会自动重新执行build,触发过滤后的列表刷新;- TextField必须绑定状态值,否则UI重建时会丢失输入内容;
- 防抖逻辑放在独立Provider中,避免业务逻辑与状态控制耦合。
内容的提问来源于stack exchange,提问作者Leonardo Maito
相关产品推荐
相关产品推荐

