You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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"),
  );
}

解决方案

核心问题拆解

  1. TextField清空问题:输入框未绑定searchTextProvider的状态值,每次UI因列表更新重建时,输入框会回到初始空值;
  2. 实时更新逻辑冗余:无需手动调用_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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 14:35:53