Flutter Riverpod问题:为何ref.refresh()在_page=1时无法重建组件?
问题描述
我是Flutter和Riverpod新手,正在实现一个从服务端获取数据的带分页和刷新功能的列表。分页功能正常,但刷新仅在_page>1时生效,当_page=1时组件不会重建。已排查数小时,能看到DriverRepository().driverJobList(tab, pages, filtering)已调用且返回新的JSON数据,但界面无变化。
import 'package:super_carga_mobileapp/styles.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../Driver/Jobs/model.dart'; import 'package:super_carga_mobileapp/utilis.dart'; import 'job_list_provider.dart'; import 'widgets/job_list_element.dart'; class JobListPage extends ConsumerWidget { List<JobListProperties> jobPropertiesList = []; late int _maxPages; int _page =1; @override Widget build(BuildContext context, WidgetRef ref) { late ScrollController _scrollController; final jobListAsyncValue = ref.watch(JobListResponseProvider); void _loadMore() { if (_scrollController.position.atEdge) { if (_scrollController.position.extentAfter < 500) { if (_page < _maxPages) { ref.read(JobListPaginationProvider.notifier).state = ++_page; } } } } _scrollController = ScrollController()..addListener(_loadMore); Widget _buildList(int length) { return RefreshIndicator( onRefresh: () async { jobPropertiesList.clear(); if (_page > 1) { _page = 1; ref.read(JobListPaginationProvider.notifier).state = _page; } else { return await ref.refresh(JobListResponseProvider); } }, child: Column( children: [ Expanded( child: ListView.separated( controller: _scrollController, itemCount: length, itemBuilder: (context, index) { return JobListElement(jobPropertiesList[index]); }, separatorBuilder: (BuildContext context, int index) => const Seperator(), ), ), ], ), ); } return jobListAsyncValue.when( data: (data) { if (data.data.isNotEmpty) { jobPropertiesList.addAll(data.data.toList()); final length = jobPropertiesList.length; _maxPages = data.pages; return _buildList(length); } else { return Column( children: [ SizedBox( height: 60, ), Text( "No results", style: titleStyle, ), ], ); } }, error: (error, stackTrace) => ErrorMessage(message: error.toString()), loading: () { if (jobPropertiesList.isNotEmpty) { return _buildList(jobPropertiesList.length); } else { return PageLoadingSpinnerWidget(); } }, ); } } final JobListResponseProvider = FutureProvider.autoDispose<JobListResponse>((ref) async { final pages = ref.watch(JobListPaginationProvider); final tab = ref.watch(MiddleScreenTabProvider); final filtering = ref.watch(searchTextProvider); return await DriverRepository().driverJobList(tab, pages, filtering); }); final JobListPaginationProvider = StateProvider.autoDispose<int>((ref) { return 1; }); final searchTextProvider = StateProvider<Filtering>((ref) { return Filtering(""); }); final bottomNavIndex = StateProvider<int>((ref) { return 0; }); final DeliveryButtonProvider = StateProvider<bool>((ref) { return false; }); final PickupButtonProvider = StateProvider<bool>((ref) { return false; }); final MiddleScreenTabProvider = StateProvider<int>((ref) { return 1; });
问题分析与修复
核心问题
- Widget本地状态未重置:
jobPropertiesList、_page是JobListPage的成员变量,属于Widget实例状态。当_page=1时调用ref.refresh,Provider获取新数据后,data回调里用addAll会把新数据追加到旧列表后,而非替换,界面自然不会更新为全新数据。 - ScrollController重复创建:每次
build都会生成新的ScrollController并添加监听,会导致内存泄漏,也可能干扰分页逻辑。 - 刷新逻辑分支冗余:分
_page>1和_page=1处理刷新,导致逻辑不一致,且_page=1时仅刷新Provider但未重置本地列表。
修复方案
1. 用Riverpod管理列表状态
创建专门的Notifier管理分页列表的状态,替代Widget本地变量:
class JobListStateNotifier extends Notifier<({List<JobListProperties> items, int currentPage, int maxPages})> { @override ({List<JobListProperties> items, int currentPage, int maxPages}) build() { return (items: [], currentPage: 1, maxPages: 1); } // 重置列表到初始状态 void reset() { state = (items: [], currentPage: 1, maxPages: state.maxPages); } // 添加新页数据 void addItems(List<JobListProperties> newItems, int maxPages) { state = ( items: [...state.items, ...newItems], currentPage: state.currentPage + 1, maxPages: maxPages ); } // 更新当前页码 void setCurrentPage(int page) { state = ( items: state.items, currentPage: page, maxPages: state.maxPages ); } } final jobListStateProvider = NotifierProvider<JobListStateNotifier, ({List<JobListProperties> items, int currentPage, int maxPages})>(JobListStateNotifier.new);
2. 重构数据获取Provider
让jobListResponseProvider仅负责单页数据获取,列表拼接交给Notifier:
final jobListResponseProvider = FutureProvider.autoDispose<JobListResponse>((ref) async { final page = ref.watch(jobListStateProvider.select((state) => state.currentPage)); final tab = ref.watch(MiddleScreenTabProvider); final filtering = ref.watch(searchTextProvider); return await DriverRepository().driverJobList(tab, page, filtering); });
3. 重构JobListPage组件
改为ConsumerStatefulWidget管理ScrollController,统一刷新逻辑:
class JobListPage extends ConsumerStatefulWidget { const JobListPage({super.key}); @override ConsumerState<JobListPage> createState() => _JobListPageState(); } class _JobListPageState extends ConsumerState<JobListPage> { late final ScrollController _scrollController; @override void initState() { super.initState(); _scrollController = ScrollController()..addListener(_loadMore); } @override void dispose() { _scrollController.dispose(); super.dispose(); } void _loadMore() { final state = ref.read(jobListStateProvider); if (_scrollController.position.atEdge && _scrollController.position.extentAfter < 500) { if (state.currentPage < state.maxPages) { ref.read(jobListStateProvider.notifier).setCurrentPage(state.currentPage + 1); ref.invalidate(jobListResponseProvider); } } } Widget _buildList(List<JobListProperties> items) { return RefreshIndicator( onRefresh: () async { ref.read(jobListStateProvider.notifier).reset(); await ref.refresh(jobListResponseProvider); }, child: ListView.separated( controller: _scrollController, itemCount: items.length, itemBuilder: (context, index) => JobListElement(items[index]), separatorBuilder: (_, __) => const Seperator(), ), ); } @override Widget build(BuildContext context, WidgetRef ref) { // 监听数据变化,自动添加到列表 ref.listen<AsyncValue<JobListResponse>>(jobListResponseProvider, (previous, next) { next.whenData((data) { if (data.data.isNotEmpty) { ref.read(jobListStateProvider.notifier).addItems(data.data.toList(), data.pages); } }); }); final jobListState = ref.watch(jobListStateProvider); final jobListAsyncValue = ref.watch(jobListResponseProvider); return jobListAsyncValue.when( data: (_) { return jobListState.items.isNotEmpty ? _buildList(jobListState.items) : Column( children: [ const SizedBox(height: 60), Text("No results", style: titleStyle), ], ); }, error: (error, _) => ErrorMessage(message: error.toString()), loading: () { return jobListState.items.isNotEmpty ? _buildList(jobListState.items) : const PageLoadingSpinnerWidget(); }, ); } }
4. 额外优化
- 所有Provider命名改为小驼峰(如
jobListResponseProvider),符合Dart规范 - 移除冗余的
JobListPaginationProvider,分页状态由jobListStateProvider统一管理 - 刷新逻辑统一,无需分页码处理,重置状态后直接刷新第一页数据
内容的提问来源于stack exchange,提问作者user1618809
相关产品推荐
相关产品推荐

