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

Flutter Bloc/Cubit如何回退至前状态?登录加载状态未消失问题

问题描述

我有一个signIn函数,会从API获取数据:请求成功则跳转至其他页面,失败则显示AlertDialog。发起请求前,我会切换至WaitingSignInAuth状态,显示CircularProgressIndicator提示用户正在加载数据。但关闭错误提示的AlertDialog后,CircularProgressIndicator并未消失,我希望移除WaitingSignInAuth状态,回到带有输入框的Scaffold页面。此外,触发错误时我已emit ErrorSignInAuth状态,为何WaitingSignInAuth状态仍存在?ErrorSignInAuth为何未替换它,还是其工作逻辑本就如此?

相关代码:

@override
Widget build(BuildContext context) {
    return BlocConsumer<AuthCubit, AuthState>(
      listener: (context, state) {
        if (state is WaitingSignInAuth) {
          showDialog(
              context: context,
              builder: (context) => Container(
                    width: MediaQuery.of(context).size.width,
                    height: MediaQuery.of(context).size.height,
                    color: Colors.black.withOpacity(0.6),
                    child: const Center(
                      child: CircularProgressIndicator(
                        strokeWidth: 1,
                        color: Colors.black,
                        backgroundColor: Colors.white,
                      ),
                    ),
                  ));
        }
        if (state is ErrorSignInAuth) {
          showDialog(
            context: context,
            builder: (BuildContext context) {
              return AlertDialog(
                title: Text("Bad request"),
                content: SingleChildScrollView(
                  child: ListBody(
                    children: <Widget>[
                      Text("Error")
                    ],
                  ),
                ),
                actions: <Widget>[
                  TextButton(
                    child: const Text("Close"),
                    onPressed: () {
                      Navigator.pop(context); // 点击关闭后AlertDialog消失,但加载圈还在
                    },
                  )
                ],
              );
            }
          );
        }
      },
    );
}

效果截图:
加载弹窗未消失的错误状态

问题原因

  1. Bloc状态是替换式的:ErrorSignInAuth已经完全替换了WaitingSignInAuth状态,Bloc内部不会同时存在多个状态。你看到加载圈还在,是因为加载弹窗是通过showDialog打开的独立弹窗,状态切换后没有主动关闭它。
  2. 弹窗入栈逻辑:showDialog会将弹窗加入导航栈,WaitingSignInAuth触发的加载弹窗是第一个栈元素,ErrorSignInAuth触发的错误弹窗是第二个栈元素。关闭错误弹窗只是弹出了第二个栈元素,加载弹窗仍在栈中,所以不会消失。

解决方案

方案一:切换到错误状态时先关闭加载弹窗

在进入ErrorSignInAuth状态的分支里,先调用Navigator.pop(context)关闭之前的加载弹窗,再打开错误提示弹窗:

@override
Widget build(BuildContext context) {
    return BlocConsumer<AuthCubit, AuthState>(
      listener: (context, state) {
        if (state is WaitingSignInAuth) {
          showDialog(
              context: context,
              barrierDismissible: false, // 禁止点击空白关闭加载弹窗
              builder: (context) => Container(
                    width: MediaQuery.of(context).size.width,
                    height: MediaQuery.of(context).size.height,
                    color: Colors.black.withOpacity(0.6),
                    child: const Center(
                      child: CircularProgressIndicator(
                        strokeWidth: 1,
                        color: Colors.black,
                        backgroundColor: Colors.white,
                      ),
                    ),
                  ));
        }
        if (state is ErrorSignInAuth) {
          // 先关闭加载弹窗
          Navigator.pop(context);
          showDialog(
            context: context,
            builder: (BuildContext context) {
              return AlertDialog(
                title: const Text("Bad request"),
                content: const SingleChildScrollView(
                  child: ListBody(
                    children: <Widget>[
                      Text("Error")
                    ],
                  ),
                ),
                actions: <Widget>[
                  TextButton(
                    child: const Text("Close"),
                    onPressed: () {
                      Navigator.pop(context);
                    },
                  )
                ],
              );
            }
          );
        }
      },
    );
}

方案二:用BlocBuilder管理加载UI(更推荐)

避免在listener里处理弹窗,而是用BlocBuilder直接在页面中渲染加载遮罩,这样状态切换时UI会自动更新:

@override
Widget build(BuildContext context) {
  return Stack(
    children: [
      // 原有的登录页面Scaffold
      Scaffold(
        // 你的输入框、登录按钮等内容
      ),
      // 根据状态渲染加载遮罩
      BlocBuilder<AuthCubit, AuthState>(
        builder: (context, state) {
          if (state is WaitingSignInAuth) {
            return Container(
              width: MediaQuery.of(context).size.width,
              height: MediaQuery.of(context).size.height,
              color: Colors.black.withOpacity(0.6),
              child: const Center(
                child: CircularProgressIndicator(
                  strokeWidth: 1,
                  color: Colors.black,
                  backgroundColor: Colors.white,
                ),
              ),
            );
          }
          return const SizedBox.shrink();
        },
      ),
      // 错误弹窗的监听
      BlocListener<AuthCubit, AuthState>(
        listener: (context, state) {
          if (state is ErrorSignInAuth) {
            showDialog(
              context: context,
              builder: (BuildContext context) {
                return AlertDialog(
                  title: const Text("Bad request"),
                  content: const SingleChildScrollView(
                    child: ListBody(
                      children: <Widget>[
                        Text("Error")
                      ],
                    ),
                  ),
                  actions: <Widget>[
                    TextButton(
                      child: const Text("Close"),
                      onPressed: () {
                        Navigator.pop(context);
                      },
                    )
                  ],
                );
              }
            );
          }
        },
      ),
    ],
  );
}

补充说明

  • 方案二的优势是UI状态完全由Bloc状态驱动,不需要手动管理弹窗的打开和关闭,状态切换时加载遮罩会自动消失,更符合Bloc的设计思想。
  • 如果使用showDialog的方式,建议给加载弹窗设置barrierDismissible: false,避免用户误触关闭加载弹窗导致状态不一致。

内容的提问来源于stack exchange,提问作者newbiras

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 00:55:22