如何基于Riverpod的FutureProvider实现infinite_scroll_pagination的分页与搜索
在infinite_scroll_pagination中结合Riverpod实现搜索驱动的无限滚动
要实现带搜索查询的无限滚动,核心是让分页逻辑能响应搜索条件的变化(自动重置分页、重新加载数据),我们可以用StateNotifierProvider来管理分页状态,同时监听搜索和类型的StateProvider,具体实现如下:
1. 定义分页状态类
首先创建一个类来保存分页相关的所有状态:
class AgentPaginationState { const AgentPaginationState({ required this.items, required this.currentPage, required this.hasMore, required this.isLoading, }); // 已加载的列表数据 final List<AgentPreviewModel> items; // 当前页码 final int currentPage; // 是否还有更多数据可加载 final bool hasMore; // 是否正在加载中 final bool isLoading; // 拷贝方法,用于更新状态 AgentPaginationState copyWith({ List<AgentPreviewModel>? items, int? currentPage, bool? hasMore, bool? isLoading, }) { return AgentPaginationState( items: items ?? this.items, currentPage: currentPage ?? this.currentPage, hasMore: hasMore ?? this.hasMore, isLoading: isLoading ?? this.isLoading, ); } }
2. 创建StateNotifier管理分页逻辑
实现一个StateNotifier,负责处理加载更多、监听搜索条件变化重置分页的逻辑:
class AgentPaginationNotifier extends StateNotifier<AgentPaginationState> { AgentPaginationNotifier(this.ref) : super(const AgentPaginationState( items: [], currentPage: 1, hasMore: true, isLoading: false, )) { // 监听搜索查询和类型变化,一旦变化就重置分页并重新加载第一页 ref.listen(allAgentSearchQueryProvider, (_, __) => _resetAndLoadFirstPage()); ref.listen(agentTypeQueryProvider, (_, __) => _resetAndLoadFirstPage()); } final Ref ref; // 假设每页加载的数量 static const _pageSize = 20; // 加载更多数据 Future<void> loadMore() async { if (state.isLoading || !state.hasMore) return; state = state.copyWith(isLoading: true); try { final query = ref.read(allAgentSearchQueryProvider); final type = ref.read(agentTypeQueryProvider); final api = ref.read(agentAPIProvider); final newItems = await api.getAgentsByPage( query: query, type: type, page: state.currentPage, pageSize: _pageSize, ); if (newItems.length < _pageSize) { // 返回的数据不足一页,说明没有更多数据了 state = state.copyWith( items: [...state.items, ...newItems], hasMore: false, isLoading: false, ); } else { state = state.copyWith( items: [...state.items, ...newItems], currentPage: state.currentPage + 1, isLoading: false, ); } } catch (e) { // 处理加载错误,这里可以根据需求添加错误状态 state = state.copyWith(isLoading: false); rethrow; } } // 重置分页并加载第一页 Future<void> _resetAndLoadFirstPage() async { state = const AgentPaginationState( items: [], currentPage: 1, hasMore: true, isLoading: false, ); await loadMore(); } }
3. 定义StateNotifierProvider
final agentPaginationProvider = StateNotifierProvider<AgentPaginationNotifier, AgentPaginationState>((ref) { return AgentPaginationNotifier(ref); });
4. UI层结合infinite_scroll_pagination使用
用PagedListView来实现无限滚动,结合Riverpod监听分页状态:
class AgentListScreen extends ConsumerWidget { const AgentListScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final paginationState = ref.watch(agentPaginationProvider); final notifier = ref.read(agentPaginationProvider.notifier); return PagedListView<int, AgentPreviewModel>( pagingController: PagingController(firstPageKey: 1) ..addPageRequestListener((pageKey) { // 触发加载更多 notifier.loadMore(); }), builderDelegate: PagedChildBuilderDelegate<AgentPreviewModel>( itemBuilder: (context, item, index) => AgentPreviewCard(item: item), firstPageErrorIndicatorBuilder: (context) => const Text('加载失败,请重试'), noItemsFoundIndicatorBuilder: (context) => const Text('没有找到匹配的代理人'), newPageErrorIndicatorBuilder: (context) => const Text('加载更多失败,请重试'), ), ); } }
关键逻辑说明
- 当搜索查询或类型变化时,
StateNotifier中的listen会触发_resetAndLoadFirstPage,重置分页状态并重新加载第一页数据。 loadMore方法会检查当前是否正在加载或是否还有更多数据,避免重复请求。- 后端需要支持分页接口(根据page和pageSize返回对应数据),如果你的
getAllAgents原本是返回全部数据,需要调整为分页版本。
内容的提问来源于stack exchange,提问作者Tanvir Ibn Mizan
相关产品推荐
相关产品推荐

