枚举类型BLoC事件管理及emit报错与BlocBuilder适配问题
问题解决方案
一、解决“emit was called after an event handler completed normally”错误及加载状态不更新
1. 错误原因
该错误源于Bloc事件处理函数中,异步操作未通过await等待完成,导致emit调用在事件处理函数执行结束后才触发,违反了flutter_bloc的状态更新规则。
2. 修复步骤
(1)正确处理异步事件(新版flutter_bloc写法)
确保事件处理回调用async修饰,并await所有异步操作(比如API请求):
class TmdbBloc extends Bloc<TmdbEvent, TmdbState> { final TmdbRepository tmdbRepository; TmdbBloc(this.tmdbRepository) : super(const TmdbState.loading()) { on<GetPopularMovies>((event, emit) async { emit(const TmdbState.loading()); try { final movies = await tmdbRepository.getPopularMovies(); emit(TmdbState.success(movies)); } catch (e) { emit(TmdbState.error(e.toString())); } }); } }
(2)检查状态更新逻辑
界面持续显示CircularProgressIndicator说明状态一直停留在loading,需排查:
- 确认页面初始化时已触发事件:
在页面initState中添加事件触发代码:@override void initState() { super.initState(); context.read<TmdbBloc>().add(const GetPopularMovies()); } - 确保Bloc状态覆盖所有场景(结合freezed):
用freezed定义完整状态,并在BlocBuilder中处理所有分支:// freezed状态定义示例 @freezed class TmdbState with _$TmdbState { const factory TmdbState.initial() = _Initial; const factory TmdbState.loading() = _Loading; const factory TmdbState.success(List<Movie> movies) = _Success; const factory TmdbState.error(String message) = _Error; } // BlocBuilder中处理所有状态 BlocBuilder<TmdbBloc, TmdbState>( builder: (context, state) { return state.when( initial: () => SizedBox.shrink(), loading: () => CircularProgressIndicator(), success: (movies) => ListView.builder( itemCount: movies.length, itemBuilder: (_, i) => Text(movies[i].title), ), error: (msg) => Text('出错了:$msg'), ); }, )
二、让BlocBuilder关联特定TmdbEvent(如GetPopularMovies)
BlocBuilder监听的是状态变化,而非直接关联事件,要关联特定事件的执行结果,可通过以下方式实现:
1. 为不同事件定义专属状态(结合freezed)
针对不同事件的结果,定义对应的状态类,明确区分事件触发的状态:
@freezed class TmdbState with _$TmdbState { const factory TmdbState.loading() = _Loading; // 对应GetPopularMovies事件的成功状态 const factory TmdbState.popularMoviesSuccess(List<Movie> movies) = _PopularMoviesSuccess; // 对应其他事件的成功状态 const factory TmdbState.topRatedMoviesSuccess(List<Movie> movies) = _TopRatedMoviesSuccess; const factory TmdbState.error(String message) = _Error; }
2. 在Bloc中处理事件并emit对应状态
class TmdbBloc extends Bloc<TmdbEvent, TmdbState> { final TmdbRepository repo; TmdbBloc(this.repo) : super(const TmdbState.loading()) { on<GetPopularMovies>((event, emit) async { emit(const TmdbState.loading()); try { final movies = await repo.getPopularMovies(); emit(TmdbState.popularMoviesSuccess(movies)); } catch (e) { emit(TmdbState.error(e.toString())); } }); on<GetTopRatedMovies>((event, emit) async { emit(const TmdbState.loading()); try { final movies = await repo.getTopRatedMovies(); emit(TmdbState.topRatedMoviesSuccess(movies)); } catch (e) { emit(TmdbState.error(e.toString())); } }); } }
3. 在BlocBuilder中针对性渲染UI
方式一:直接匹配状态
BlocBuilder<TmdbBloc, TmdbState>( builder: (context, state) { return state.when( loading: () => CircularProgressIndicator(), popularMoviesSuccess: (movies) => PopularMoviesList(movies: movies), topRatedMoviesSuccess: (movies) => TopRatedMoviesList(movies: movies), error: (msg) => Text(msg), ); }, )
方式二:用buildWhen过滤状态
如果只想让BlocBuilder响应GetPopularMovies事件的状态变化,可通过buildWhen限定:
BlocBuilder<TmdbBloc, TmdbState>( buildWhen: (prev, curr) { // 仅当当前状态是PopularMoviesSuccess时重新构建UI return curr is _PopularMoviesSuccess; }, builder: (context, state) { if (state is _PopularMoviesSuccess) { return PopularMoviesList(movies: state.movies); } return CircularProgressIndicator(); }, )
内容的提问来源于stack exchange,提问作者Ουιλιαμ Αρκευα
相关产品推荐
相关产品推荐

