Flutter中BlocBuilder无法接收Bloc状态的问题排查
问题描述
我有一个需要展示列表的页面,通过API获取数据并在加载阶段显示加载器。当前初始状态可正常触发加载器显示,但从Bloc发出的其他状态无法传递至页面,BlocBuilder仅响应默认的初始状态。
相关代码
页面代码
class _WeatherInfoPageState extends State<WeatherInfoPage> { late WeatherInfoBloc _weatherInfoBloc; @override void initState() { super.initState(); _weatherInfoBloc = locator<WeatherInfoBloc>(); _weatherInfoBloc.add(WeatherInfoFetchEvent()); } @override Widget build(BuildContext context) { return BlocBuilder<WeatherInfoBloc, WeatherInfoState>( builder: (context, state) { if (state is WeatherInfoInitial || state is WeatherInfoFetchingState) { return _buildLoading(); } else if (state is WeatherInfoFetchingSuccessState) { return _buildWeatherForcastView(context, state.response); } return Container(); } ); } Widget _buildWeatherForcastView(BuildContext context, WeatherInfo model) { return Container(); } Widget _buildLoading() => const Center(child: CircularProgressIndicator()); }
Bloc代码
WeatherInfoBloc(this._weatherInfoUseCase) : super(WeatherInfoInitial()) { on<WeatherInfoFetchEvent>((event, emit) async { emit(WeatherInfoFetchingState()); final response = await _weatherInfoUseCase.getWeatherInfo( "52.521992", "13.413244"); response.fold( (failure) => { emit(WeatherInfoFetchingFailureState(failure.message)) }, (success) => { emit(WeatherInfoFetchingSuccessState(success)) }); }); }
主页面代码
Widget build(BuildContext context) { return BlocProvider<WeatherInfoBloc>( create: (context) => locator<WeatherInfoBloc>(), child: MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Demo', themeMode: ThemeMode.system, theme: AppTheme.lightTheme, darkTheme: AppTheme.darkTheme, home: const Scaffold( backgroundColor: Colors.white, body: SafeArea( child: WeatherInfoPage(), ), ), )); }
问题原因
核心问题是你在页面中通过locator获取的Bloc实例,和BlocProvider提供给Widget树的实例不是同一个:
- 如果
locator中WeatherInfoBloc是按factory模式注册(每次调用locator<WeatherInfoBloc>()都会生成新实例),那么主页面BlocProvider创建的是一个实例,页面initState里拿到的是另一个独立实例。 - 你给后者发送了
WeatherInfoFetchEvent,但BlocBuilder监听的是前者的状态变化,自然无法接收到后续状态更新。
修复方案
方案1:通过Context获取Bloc实例(推荐)
不要直接用locator获取Bloc,而是从Widget树的Context中获取,确保事件发送和状态监听的是同一个实例:
修改后的页面代码:
class _WeatherInfoPageState extends State<WeatherInfoPage> { @override void initState() { super.initState(); // 等待Widget挂载到树后,从Context获取Bloc并发送事件 WidgetsBinding.instance.addPostFrameCallback((_) { context.read<WeatherInfoBloc>().add(WeatherInfoFetchEvent()); }); } @override Widget build(BuildContext context) { return BlocBuilder<WeatherInfoBloc, WeatherInfoState>( builder: (context, state) { if (state is WeatherInfoInitial || state is WeatherInfoFetchingState) { return _buildLoading(); } else if (state is WeatherInfoFetchingSuccessState) { return _buildWeatherForcastView(context, state.response); } else if (state is WeatherInfoFetchingFailureState) { // 增加失败状态的UI展示 return Center(child: Text(state.message)); } return Container(); } ); } Widget _buildWeatherForcastView(BuildContext context, WeatherInfo model) { return Container(); } Widget _buildLoading() => const Center(child: CircularProgressIndicator()); }
方案2:将Bloc注册为单例(不推荐,耦合性高)
如果一定要用locator,需确保每次获取的是同一个实例。以GetIt为例,修改注册逻辑:
// 原factory模式注册(每次生成新实例) // locator.registerFactory(() => WeatherInfoBloc(locator())); // 修改为单例模式 locator.registerSingleton<WeatherInfoBloc>(WeatherInfoBloc(locator()));
这种方式虽能解决实例不一致问题,但会增加代码耦合,不符合Bloc的最佳实践。
额外优化
- 增加
WeatherInfoFetchingFailureState的UI处理,避免请求失败时页面显示空白容器。 - 可以用
BlocProvider.of<WeatherInfoBloc>(context)替代context.read,效果一致。
内容的提问来源于stack exchange,提问作者mars_dev
相关产品推荐
相关产品推荐

