You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 12:45:17