Flutter传递isLoading值异常:始终为false无法显示加载指示器
解决StatelessWidget中isLoading不更新导致加载指示器不显示的问题
问题描述
我有一个继承自BaseStatelessWidget<HomeBloc, IGeneralState>的CTAButton类,意图通过isLoading变量控制CircularProgressIndicator的显示。点击按钮会触发BaseStatelessWidget中的事件,事件状态先转为Loading,再转为Success:加载阶段需要把isLoading设为true显示指示器,成功后跳转页面并将isLoading设为false隐藏指示器,但实际isLoading始终为false。
问题根源
- StatelessWidget的不可变性:
BaseStatelessWidget是StatelessWidget,其成员变量isLoading的变化不会触发Widget重建。即使在_onListener中修改了isLoading的值,UI也不会更新,始终显示初始的false状态。 - 状态副本不一致:手动维护的
isLoading变量和Bloc的真实状态可能出现不同步,导致UI无法反映实际加载状态。
解决方案
方案1:移除自定义isLoading,直接依赖Bloc状态(推荐)
既然已经使用Bloc管理状态,无需额外维护isLoading变量,直接从Bloc的状态中判断是否处于加载状态,通过BlocBuilder监听状态变化更新UI。
修改BaseStatelessWidget
移除isLoading成员变量,保留核心的Bloc管理逻辑:
class BaseStatelessWidget<B extends Bloc<dynamic, T>, T> extends StatelessWidget { @override BlocProvider<B> build(BuildContext context) { return BlocProvider.value( value: onCreate(context), child: WillPopScope( onWillPop: () { dispose(); return Future.value(true); }, child: _buildBaseView(context), ), ); } @protected void _onListener(context, state) { if (state is Loading) { onLoadingListener(state); } else if (state is Success) { onSuccessListener(state); } else if (state is Exception) { onExceptionListener(state); } } // 保留原有的onCreate、dispose、_buildBaseView等方法 }
修改CTAButton
使用BlocBuilder监听HomeBloc的状态,动态渲染加载指示器:
class CTAButton extends BaseStatelessWidget<HomeBloc<HomeState>, IGeneralState<HomeState>> { final String text; final VoidCallback? onPressed; final double indicatorSize; final BorderSide border; final Color mainColor; CTAButton({ required this.text, this.onPressed, this.indicatorSize = 24, required this.border, required this.mainColor, }); @override Widget build(BuildContext context) { return Center( child: Container( alignment: Alignment.center, child: PhysicalModel( elevation: 4, shadowColor: Colors.black, color: mainColor, borderRadius: BorderRadius.circular(25), child: SizedBox( height: 50, width: double.infinity, child: ElevatedButton( elevation: MaterialStateProperty.all<double>(0), shape: MaterialStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(25), side: border, ), ), child: BlocBuilder<HomeBloc<HomeState>, IGeneralState<HomeState>>( builder: (context, state) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (state is Loading) SizedBox( height: indicatorSize, width: indicatorSize, child: const Padding( padding: EdgeInsets.all(10.0), child: CircularProgressIndicator( value: null, valueColor: AlwaysStoppedAnimation<Color>(Colors.white), ), ), ), if (state is Loading) const SizedBox(width: 10), Text(text, style: const TextStyle(color: Colors.white)), ], ); }, ), onPressed: onPressed == null ? null : () => onPressed!(), ), ), ), ), ); } }
保留HomeScreen调用逻辑
确保事件触发后,Bloc能正确发出Loading和Success状态即可:
class HomeScreen extends BaseStatelessWidget<HomeBloc<HomeState>, IGeneralState<HomeState>> { @override Widget build(BuildContext context) { // 其他页面代码... return Details( key: const Key("DetailsStart"), onTap: () { broadcastEvent(ArriveToLocationEvent(/* 事件参数 */)); broadcastEvent(RefreshMarkersEvent(pageBloc.homeState.homeMarkers)); }, ); } }
方案2:将Base类改为StatefulWidget(不推荐)
如果必须在Base类中维护isLoading,需要将BaseStatelessWidget改为StatefulWidget,利用State的setState触发UI重建:
abstract class BaseStatefulWidget<B extends Bloc<dynamic, T>, T> extends StatefulWidget { const BaseStatefulWidget({Key? key}) : super(key: key); B onCreate(BuildContext context); void dispose(); Widget _buildBaseView(BuildContext context); void onLoadingListener(T state); void onSuccessListener(T state); void onExceptionListener(T state); @override State<BaseStatefulWidget<B, T>> createState() => _BaseStatefulWidgetState<B, T>(); } class _BaseStatefulWidgetState<B extends Bloc<dynamic, T>, T> extends State<BaseStatefulWidget<B, T>> { late B bloc; bool isLoading = false; @override void initState() { super.initState(); bloc = widget.onCreate(context); bloc.stream.listen(_onListener); } void _onListener(T state) { setState(() { if (state is Loading) { isLoading = true; widget.onLoadingListener(state); } else if (state is Success || state is Exception) { isLoading = false; state is Success ? widget.onSuccessListener(state) : widget.onExceptionListener(state); } }); } @override Widget build(BuildContext context) { return BlocProvider.value( value: bloc, child: WillPopScope( onWillPop: () { widget.dispose(); return Future.value(true); }, child: widget._buildBaseView(context), ), ); } }
之后让CTAButton继承这个BaseStatefulWidget,在UI中通过widget.isLoading(需在State类中暴露该变量)判断是否显示指示器。
关键注意事项
- StatelessWidget的成员变量是不可变的,状态变化必须依赖StatefulWidget或状态管理工具(如Bloc)
- 使用Bloc时,UI应直接响应Bloc的状态,避免维护独立的状态副本,防止状态不一致
- 确保Bloc在事件处理流程中正确发出
Loading状态,否则UI无法监听到加载状态变化
内容的提问来源于stack exchange,提问作者m3n
相关产品推荐
相关产品推荐

