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

Flutter传递isLoading值异常:始终为false无法显示加载指示器

解决StatelessWidget中isLoading不更新导致加载指示器不显示的问题

问题描述

我有一个继承自BaseStatelessWidget<HomeBloc, IGeneralState>的CTAButton类,意图通过isLoading变量控制CircularProgressIndicator的显示。点击按钮会触发BaseStatelessWidget中的事件,事件状态先转为Loading,再转为Success:加载阶段需要把isLoading设为true显示指示器,成功后跳转页面并将isLoading设为false隐藏指示器,但实际isLoading始终为false。

问题根源

  1. StatelessWidget的不可变性:BaseStatelessWidget是StatelessWidget,其成员变量isLoading的变化不会触发Widget重建。即使在_onListener中修改了isLoading的值,UI也不会更新,始终显示初始的false状态。
  2. 状态副本不一致:手动维护的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 23:31:46