Flutter中Riverpod+Stream出现重复Loading事件问题求助
Flutter Riverpod + Stream 重复接收Loading事件问题排查与解决
核心问题分析
从你提供的代码和描述来看,仅Loading事件重复触发、Success/Error事件正常,大概率是以下两种情况之一:
getToiletListFromRemote()方法被调用了两次,导致_uiEventController.add(const ToiletListUiEvent.onLoading())执行两次- Stream被重复订阅,单次事件被多个订阅者接收,造成重复触发的假象
排查步骤
1. 确认方法是否被重复调用
在getToiletListFromRemote()的第一行添加日志打印,验证调用次数:
Future getToiletListFromRemote() async { print("=== 调用getToiletListFromRemote ==="); // 新增日志 _uiEventController.add(const ToiletListUiEvent.onLoading()); // ... 原有代码 }
如果控制台打印两次,说明方法被重复调用。此时需要检查:
- 是否在
initState之外的地方(如页面重建、按钮点击等)也调用了该方法 - 父组件是否导致当前Widget的
initState执行了两次(比如未使用稳定的Key,或页面路由跳转后重建)
2. 检查Stream订阅是否重复
当前代码未取消Stream订阅,若Widget因重建(如父组件rebuild)导致initState再次执行,会新增一个Stream订阅,此时单次事件会被多个订阅者处理,看起来像是事件重复。
解决方案
方案1:取消Stream订阅
在State类中保存StreamSubscription,并在dispose时取消,避免重复订阅:
late StreamSubscription _uiEventSubscription; @override void initState() { super.initState(); // ... 原有代码 _uiEventSubscription = toiletListViewModel.uiEventStream.listen((event) { event.when( onLoading: _onLoading, onError: _onError, onSuccess: _onSuccess, ); }); // ... 原有代码 } @override void dispose() { _uiEventSubscription.cancel(); // 取消订阅 super.dispose(); }
方案2:确保数据加载方法只执行一次
在ViewModel中添加加载状态标记,避免重复触发加载逻辑:
// 在ToiletListViewModel中添加标记 bool _isRemoteLoading = false; Future getToiletListFromRemote() async { if (_isRemoteLoading) return state; // 避免重复加载 _isRemoteLoading = true; _uiEventController.add(const ToiletListUiEvent.onLoading()); try { List<Toilet> results = await getToiletListFromRemoteUseCase(toiletListPage); state = [...state, ...results]; _uiEventController.add(const ToiletListUiEvent.onSuccess()); saveToiletList(state); } catch (e) { e as DioError; _uiEventController.add(ToiletListUiEvent.onError(e.message)); } finally { _isRemoteLoading = false; // 重置标记 } return state; }
方案3:改用Riverpod AsyncValue替代自定义Stream
Riverpod的AsyncValue本身封装了加载、成功、错误状态,无需手动维护StreamController,能从根源避免这类重复事件问题:
// ViewModel定义 class ToiletListViewModel extends StateNotifier<AsyncValue<List<Toilet>>> { ToiletListViewModel() : super(const AsyncValue.loading()); Future<void> getToiletListFromRemote() async { try { state = const AsyncValue.loading(); // 触发加载状态 List<Toilet> results = await getToiletListFromRemoteUseCase(toiletListPage); state = AsyncValue.data(results); saveToiletList(results); } catch (e) { state = AsyncValue.error(e, StackTrace.current); } } } // 页面监听状态 @override Widget build(BuildContext context) { final toiletListState = ref.watch(toiletListViewModelProvider); return toiletListState.when( loading: () => _onLoading(), error: (e, _) => _onError(e.toString()), data: (list) { // 执行成功逻辑 _onSuccess(); return ListView(...); }, ); }
内容的提问来源于stack exchange,提问作者Queen Ellery
相关产品推荐
相关产品推荐

