使用Riverpod和Algolia管理搜索界面状态的问题
问题:Flutter+Algolia+Riverpod日期筛选后结果不更新
现象
开发基于Algolia的搜索界面,支持关键词搜索和日期筛选。修改查询词时结果列表正常更新,但修改开始/结束日期后,结果列表无变化;只有修改日期后再更新查询词,筛选才生效。
所有参数更新时都调用同一代码:
ref.read(comboSearchQueryNotifierProvider.notifier).setQuery(_controller.text, startDate: _startDate.value, endDate: _endDate.value,);
核心代码片段
Riverpod仓库代码
part 'combo_search_repository.g.dart'; class ComboSearchQuery { final String query; final DateTime? startDate; final DateTime? endDate; const ComboSearchQuery({ required this.query, this.startDate, this.endDate, }); ComboSearchQuery copyWith({ String? query, DateTime? startDate, DateTime? endDate, }) { return ComboSearchQuery( query: query ?? this.query, startDate: startDate ?? this.startDate, endDate: endDate ?? this.endDate, ); } @override bool operator ==(Object other) { if (identical(this, other)) return true; return other is ComboSearchQuery && other.query == query && other.startDate == startDate && other.endDate == endDate; } @override int get hashCode => Object.hash(query, startDate, endDate); } class ComboSearchRepository { ComboSearchRepository() : _multiSearcher = MultiSearcher( applicationID: Env.algoliaAppId, apiKey: Env.algoliaSearchKey, ) { _eventsSearcher = _multiSearcher.addHitsSearcher( initialState: const SearchState( indexName: 'tm_events_index', ), ); _usersSearcher = _multiSearcher.addHitsSearcher( initialState: const SearchState( indexName: 'users', ), ); } final MultiSearcher _multiSearcher; late final HitsSearcher _eventsSearcher; late final HitsSearcher _usersSearcher; Future<List<dynamic>> search(ComboSearchQuery query) async { print('Searching in combo repo for: query: ${query.query} startDate: ${query.startDate} endDate: ${query.endDate}'); if (query.query.isEmpty) { print('Search text is empty, returning []'); return []; } List<String> filters = []; if (query.startDate != null) { filters.add('date>=${query.startDate!.millisecondsSinceEpoch}'); } if (query.endDate != null) { filters.add('date<=${query.endDate!.millisecondsSinceEpoch}'); } _eventsSearcher.applyState((state) => state.copyWith( query: query.query, numericFilters: filters, )); _usersSearcher.applyState((state) => state.copyWith( query: query.query )); final eventResponse = await _eventsSearcher.responses.first; final userResponse = await _usersSearcher.responses.first; // process stream results (removed for brevity) return results; } } @Riverpod(keepAlive: true) ComboSearchRepository comboSearchRepository(ComboSearchRepositoryRef ref) { return ComboSearchRepository(); } @riverpod Future<List<dynamic>> comboListSearch(ComboListSearchRef ref, ComboSearchQuery query) async { if (query.query.isNotEmpty || query.startDate != null || query.endDate != null) { final searchRepository = ref.watch(comboSearchRepositoryProvider); return searchRepository.search(query); } else { return []; } } @riverpod Future<List<dynamic>> comboSearchResults(ComboSearchResultsRef ref) { final searchQuery = ref.watch(comboSearchQueryNotifierProvider); return ref.watch(comboListSearchProvider(searchQuery).future); }
查询通知器代码
part 'combo_search_query_notifier.g.dart'; @riverpod class ComboSearchQueryNotifier extends _$ComboSearchQueryNotifier { final _searchQueryController = StreamController<ComboSearchQuery>(); late final StreamSubscription<ComboSearchQuery> _subscription; @override ComboSearchQuery build() { _subscription = _searchQueryController.stream .debounceTime(const Duration(milliseconds: 200)) .listen(_updateState); ref.onDispose(() { _searchQueryController.close(); _subscription.cancel(); }); return ComboSearchQuery(query: '', startDate: null, endDate: null); } void _updateState(ComboSearchQuery query) { state = ComboSearchQuery( query: query.query, startDate: query.startDate, endDate: query.endDate, ); } void setQuery(String query, {DateTime? startDate, DateTime? endDate}) { final newQuery = ComboSearchQuery( query: query, startDate: startDate, endDate: endDate, ); state = newQuery; // Force Riverpod to detect a state change } } @riverpod Future<List<dynamic>> comboSearchResults(ComboSearchResultsRef ref) { final searchQuery = ref.watch(comboSearchQueryNotifierProvider); debugPrint('Updated search query in comboSearchResults: $searchQuery'); return ref.watch(comboListSearchProvider(searchQuery).future); }
问题原因
- Algolia HitsSearcher流缓存:仅修改日期时,
_eventsSearcher的查询词未变,responses.first返回的是之前缓存的流结果,没有触发新的搜索请求。 - Riverpod重复Provider定义:代码中两次定义
comboSearchResultsProvider,导致状态监听逻辑冲突,日期更新的状态未能正确传递到结果列表。 - Riverpod缓存复用:
comboListSearchProvider作为带参数的FutureProvider,当ComboSearchQuery仅日期变化时,Riverpod可能复用已完成的Future缓存结果。
解决方案
1. 强制Algolia刷新搜索
在search方法中更新筛选条件后,调用refresh()触发新的搜索请求:
Future<List<dynamic>> search(ComboSearchQuery query) async { // ... 原有筛选逻辑 ... _eventsSearcher.applyState((state) => state.copyWith( query: query.query, numericFilters: filters, )); // 强制刷新事件搜索器 _eventsSearcher.refresh(); _usersSearcher.applyState((state) => state.copyWith( query: query.query )); // 强制刷新用户搜索器 _usersSearcher.refresh(); final eventResponse = await _eventsSearcher.responses.first; final userResponse = await _usersSearcher.responses.first; // ... 结果处理逻辑 ... }
2. 移除重复的Provider定义
删除查询通知器代码中重复的comboSearchResultsProvider定义,仅保留Riverpod仓库中的那一个,确保状态监听逻辑唯一。
3. 禁用Riverpod Future缓存
在comboListSearchProvider中禁用缓存,确保参数变化时重新执行搜索:
@riverpod Future<List<dynamic>> comboListSearch(ComboListSearchRef ref, ComboSearchQuery query) async { // 禁用缓存,每次参数变化都重新发起请求 ref.keepAlive(false); if (query.query.isNotEmpty || query.startDate != null || query.endDate != null) { final searchRepository = ref.watch(comboSearchRepositoryProvider); return searchRepository.search(query); } else { return []; } }
验证步骤
- 先删除重复的
comboSearchResultsProvider定义 - 在
search方法中添加refresh()调用 - 测试仅修改日期的场景,确认结果列表是否同步更新
内容的提问来源于stack exchange,提问作者Stalfurion
相关产品推荐
相关产品推荐

