Riverpod中FutureProvider刷新时不触发loading状态的问题
问题
在Flutter开发中使用Riverpod的FutureProvider实现API调用更新UI,首次加载时能正常显示CircularProgressIndicator,加载完成后主页展示数据。但后续从其他页面调用ref.refresh(calendarResponseProvider)刷新该Provider时,主页UI能成功更新数据,却不会再次显示加载指示器,而是等待请求完成后直接更新。请问为何第二次刷新时不会进入loading状态?
相关代码
Provider定义
final getCalendarApiProvider = Provider<GetCalendarApi>( (ref) => GetCalendarApi(), ); final calendarResponseProvider = FutureProvider<List<CalendarResponse>>((ref) async { return ref.read(getCalendarApiProvider).getCalendar(ref.read(startDateProvider), ref.read(endDateProvider)); });
主页面代码
class MainMenuScreen extends ConsumerStatefulWidget { const MainMenuScreen({ Key? key, }) : super(key: key); @override ConsumerState createState() => _MainMenuScreenState(); } class _MainMenuScreenState extends ConsumerState<MainMenuScreen> { String _selectedDate = ''; @override Widget build(BuildContext context) { print('Main Build Called'); AsyncValue<List<CalendarResponse>> calendarResponse = ref.watch(calendarResponseProvider); return Scaffold( appBar: AppBar( automaticallyImplyLeading: true, title: const Text( "My Sessions", style: TextStyle(fontSize: 18, fontWeight: FontWeight.normal), ), actions: [ IconButton( icon: Icon( Icons.settings, color: Colors.white, ), onPressed: () { ref.refresh(calendarResponseProvider); }, ) ], centerTitle: true, ), drawer: const DrawerWidget(), body: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ StartEndDateWidget(), calendarResponse.when(data: (data) { print('DATA length: ${data.length}'); return Expanded( child: ListView.builder( scrollDirection: Axis.vertical, shrinkWrap: true, itemCount: data.length, itemBuilder: ((context, index) { return Text(data[index].studentNames ?? '-'); })), ); }, error: ((error, stackTrace) { return Text('Error: ${error.toString()}'); }), loading: (() { return const Center( child: CircularProgressIndicator(), ); })) ], ), ); } }
API调用类
class GetCalendarApi { Future<List<CalendarResponse>> getCalendar(DateTime startTime, DateTime endTime) async { log('startTime $startTime', name: logGetCalendarApi); log('endTime $endTime', name: logGetCalendarApi); String token = await SharedPrefsMethods.getStringFromPrefs(PREF_TOKEN_KEY); String url = BASE_URL + SESSNOTE_URL + GET_CALENDAR_URL; List<CalendarResponse> calendarResponseList; Map<String, String> queryParameters = { "startTime": "${formatDate(startTime)}", "endTime": "${formatDate(endTime)}", }; Uri uri = Uri.parse(url).replace(queryParameters: queryParameters); log('Uri: (logGetCalendarApi) $uri', name: logGetCalendarApi); return http.get(uri, headers: { HttpHeaders.contentTypeHeader: "application/json", HttpHeaders.authorizationHeader: "Bearer $token", 'accept': 'text/plain' }).then((http.Response response) { int statusCode = response.statusCode; log('Status Code: $statusCode', name: logGetCalendarApi); log('RESPONSE BODY (get students and mandates): ${response.body}', name: logGetCalendarApi); dynamic data = json.decode(response.body); if (statusCode < 200 || statusCode >= 400) { log('Error: ${response.body}', name: logGetCalendarApi); Fluttertoast.showToast( msg: data['Message'] ?? 'An internal error has occurred.', toastLength: Toast.LENGTH_LONG, timeInSecForIosWeb: 3); throw Exception(data['Message'] ?? 'An internal error has occurred.'); } calendarResponseList = data .map<CalendarResponse>( (json) => CalendarResponse.fromJson(json)) .toList(); log('Calendar result List Size: ${calendarResponseList.length}', name: logGetCalendarApi); return calendarResponseList; }); } }
其他页面刷新触发代码
ref.refresh(calendarResponseProvider);
原因分析
- Riverpod的
FutureProvider在调用ref.refresh()时,默认会保留之前缓存的数据状态,直到新的Future请求完成。这是框架为了避免UI闪烁、提升用户体验设计的行为。 - 首次加载时,Provider没有缓存数据,直接进入
loading状态;后续刷新时,Provider会先返回旧的data状态,直到新请求完成才更新为新数据,因此不会触发loading分支。
解决方案
方法1:利用AsyncValue.isRefreshing显示加载状态
在when方法的data分支中,通过calendarResponse.isRefreshing判断是否正在刷新,在原有数据上叠加加载指示器:
calendarResponse.when( data: (data) { return Stack( children: [ Expanded( child: ListView.builder( scrollDirection: Axis.vertical, shrinkWrap: true, itemCount: data.length, itemBuilder: ((context, index) { return Text(data[index].studentNames ?? '-'); }), ), ), // 刷新时显示加载指示器 if (calendarResponse.isRefreshing) const Center(child: CircularProgressIndicator()), ], ); }, error: ((error, stackTrace) { return Text('Error: ${error.toString()}'); }), loading: (() { return const Center(child: CircularProgressIndicator()); }), )
方法2:用family修饰Provider强制触发loading
如果需要刷新时完全替换为加载状态(不保留旧数据),可以给FutureProvider添加family修饰,每次刷新传入唯一标识(如时间戳),让Provider创建新实例:
- 修改Provider定义:
final calendarResponseProvider = FutureProvider.family<List<CalendarResponse>, int>((ref, refreshKey) async { return ref.read(getCalendarApiProvider).getCalendar(ref.read(startDateProvider), ref.read(endDateProvider)); });
- 主页面监听与刷新:
class _MainMenuScreenState extends ConsumerState<MainMenuScreen> { int _refreshKey = 0; @override Widget build(BuildContext context) { AsyncValue<List<CalendarResponse>> calendarResponse = ref.watch(calendarResponseProvider(_refreshKey)); return Scaffold( appBar: AppBar( actions: [ IconButton( icon: const Icon(Icons.settings, color: Colors.white), onPressed: () { setState(() { _refreshKey++; // 更新key触发Provider重建 }); }, ) ], // 其他代码... ), // 其他代码... ); } }
方法3:手动管理刷新状态
新增一个StateProvider标记刷新状态,在调用refresh前后更新状态:
- 定义状态Provider:
final isCalendarRefreshingProvider = StateProvider<bool>((ref) => false);
- 触发刷新时更新状态:
// 其他页面刷新逻辑 ref.read(isCalendarRefreshingProvider.notifier).state = true; try { await ref.refresh(calendarResponseProvider.future); } finally { ref.read(isCalendarRefreshingProvider.notifier).state = false; }
- 主页面监听并显示加载:
final isRefreshing = ref.watch(isCalendarRefreshingProvider); AsyncValue<List<CalendarResponse>> calendarResponse = ref.watch(calendarResponseProvider); // 在data分支中根据isRefreshing显示加载指示器
内容的提问来源于stack exchange,提问作者RJB
相关产品推荐
相关产品推荐

