Flutter adaptive_scaffold mediumAndUp模式下报错:Cannot emit new states after calling close
问题分析
这个错误Bad state: Cannot emit new states after calling close的核心原因是:RaceDetailBloc实例已经被销毁(调用了close),但它内部的异步操作(比如readRaceFull里的网络请求)完成后仍尝试发送新状态。
结合你的场景来看:
- 小屏模式下,详情页通过
Navigator跳转,Bloc的生命周期和路由页面绑定,操作逻辑闭环,所以正常。 - 中大屏分屏模式下,详情页作为
secondaryBody嵌入主页,可能存在Bloc被意外销毁、重复创建,或者异步操作未检查Bloc活跃状态的情况;而从小屏切到大屏时详情能正常显示,说明初始渲染时Bloc的创建和状态发送是正常的,问题出在点击列表项触发的二次数据加载逻辑。
解决方案
1. 修正分屏模式下Bloc的作用域
确保中大屏模式下,RaceDetailBloc的生命周期和主页绑定,而非详情页内部。避免每次点击列表项时重复创建/销毁Bloc:
// home_page.dart 中 mediumAndUp 模式的布局 Row( children: [ Expanded(child: RaceListView()), Expanded( // 将 BlocProvider 提升到主页层级,而非详情页内部 child: BlocProvider( create: (context) => RaceDetailBloc(repository: context.read()), child: RaceDetailView(), ), ), ], )
2. 在readRaceFull方法中添加活跃状态检查
在异步操作前后,判断Bloc是否已关闭,避免无效的状态发送:
// race_detail_bloc.dart Future<void> readRaceFull(String raceId) async { // 先检查 Bloc 是否已关闭,直接终止操作 if (isClosed) return; emit(RaceDetailLoading()); try { final race = await repository.getRaceFull(raceId); // 异步操作完成后再次检查,防止期间 Bloc 被关闭 if (!isClosed) { emit(RaceDetailLoaded(race)); } } catch (e) { if (!isClosed) { emit(RaceDetailError(e.toString())); } } }
3. 统一不同屏幕模式下的点击逻辑
确保中大屏模式下点击列表项时,仅触发Bloc的事件,而非跳转路由(避免路由跳转导致旧Bloc被销毁):
// race_entry_item_view_loaded.dart 中的点击处理 onTap: () { final isSmall = AdaptiveLayout.of(context).isSmall; if (isSmall) { Navigator.pushNamed(context, Routes.raceDetail, arguments: raceId); } else { // 直接通知已存在的 RaceDetailBloc 加载数据 context.read<RaceDetailBloc>().readRaceFull(raceId); // 或者通过 NavigationCubit 同步选中状态 context.read<NavigationCubit>().selectRace(raceId); } }
4. 检查NavigationCubit的状态同步逻辑
如果通过NavigationCubit管理选中的赛事ID,确保中大屏模式下,详情页监听NavigationCubit的状态变化时,是调用RaceDetailBloc的readRaceFull方法,而非重建Bloc实例。
内容的提问来源于stack exchange,提问作者Mauro Bocanegra
相关产品推荐
相关产品推荐

