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

Flutter侧边栏Drawer仅能打开一次 再次打开显示圆形进度指示器

问题根因

  1. 冗余监听同一条数据流:页面Body和Drawer各使用了一个StreamBuilder监听authBloc.currentUser用户流,Drawer是Scaffold的懒加载组件,首次打开时初始化,关闭后组件会被销毁。如果你的AuthBloc中currentUser没有使用可缓存最新值的可观察对象(比如RxDart的BehaviorSubject)实现,第二次打开Drawer重新监听流时,无法获取到之前已经发射过的用户数据,就会一直显示加载指示器。
  2. 数据没有复用:同一个页面内重复请求相同的用户数据,增加不必要的性能开销。

最优优化方案

直接复用Body层StreamBuilder拿到的用户数据,删掉Drawer内的独立StreamBuilder,将整个Scaffold放到全局用户数据流的监听回调中,所有子组件直接共用已经拿到的用户实例即可,修改后核心代码如下:

@override
Widget build(BuildContext context) {
  final authBloc = Provider.of<AuthBloc>(context);
  Size size = MediaQuery.of(context).size;
  // 全局监听用户流,统一处理加载、业务逻辑
  return StreamBuilder<User?>(
    stream: authBloc.currentUser,
    builder: (context, snapshot) {
      // 加载状态统一处理
      if (!snapshot.hasData) {
        return Scaffold(
          backgroundColor: Color.fromRGBO(70, 212, 153, 0.8),
          body: const Center(child: CircularProgressIndicator()),
        );
      }
      // 拿到全局可用的当前用户实例,页面所有模块直接复用
      final User currentUser = snapshot.data!;
      return Scaffold(
        backgroundColor: Color.fromRGBO(70, 212, 153, 0.8),
        appBar: AppBar(
          elevation: 0.0,
          backgroundColor: Color.fromRGBO(70, 212, 153, 0.0),
          actions: [
            GestureDetector(
              child: Container(
                margin: const EdgeInsets.only(right: 10),
                child: const Icon(
                  Icons.notifications_rounded,
                  color: Colors.white,
                ),
              ),
            )
          ],
        ),
        // 原有Body内容,所有用到snapshot.data的地方替换为currentUser即可
        body: Container(
          decoration: BoxDecoration(
            color: Colors.grey[100],
            borderRadius: const BorderRadius.only(
              topRight: Radius.circular(30),
              topLeft: Radius.circular(30),
            ),
          ),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Container(
                margin: const EdgeInsets.only(top: 20, left: 20),
                child: Text(
                  currentUser.displayName!,
                  style: const TextStyle(
                    color: Color(0xff363636),
                    fontSize: 25,
                    fontFamily: 'Roboto',
                  ),
                ),
              ),
              // 其余原有Body内容保持不变,统一替换snapshot.data为currentUser
            ],
          ),
        ),
        // Drawer直接复用已拿到的用户数据,无需单独监听流
        drawer: Drawer(
          elevation: 0,
          child: ListView(
            padding: EdgeInsets.zero,
            children: [
              UserAccountsDrawerHeader(
                accountName: Text(currentUser.displayName!),
                accountEmail: Text(currentUser.email!),
                decoration: const BoxDecoration(color: Color.fromRGBO(70, 212, 153, 1)),
                currentAccountPicture: CircleAvatar(
                  backgroundImage: NetworkImage(currentUser.photoURL!),
                ),
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  TextButton(
                    onPressed: () => authBloc.logout(),
                    child: const Text(
                      "Logout",
                      style: TextStyle(fontSize: 20),
                    ),
                  ),
                  IconButton(
                    onPressed: () => authBloc.logout(),
                    icon: const Icon(Icons.logout),
                  )
                ],
              )
            ],
          ),
        ),
      );
    },
  );
}

其他可选优化

如果你不想调整现有页面结构,可以修改AuthBloc中currentUser的实现,使用RxDart的BehaviorSubject包裹用户流,它会自动缓存最新的流数据,每次有新的监听者订阅时都会立刻发射最新值,也可以解决二次打开Drawer拿不到数据的问题,但该方案没有解决冗余监听的问题,更推荐第一种方案。

额外兼容优化

你当前initState中的登录状态监听可以增加mounted判断,避免页面销毁后执行跳转抛出异常:

@override
void initState() {
  var authBloc = Provider.of<AuthBloc>(context, listen: false);
  loginStateSubscription = authBloc.currentUser.listen((fbUser) {
    // 增加mounted判断,页面未销毁才执行跳转逻辑
    if (fbUser == null && mounted) {
      Navigator.pushReplacement(
        context,
        MaterialPageRoute(builder: (context) => const HomePage()),
      );
    }
  });
  super.initState();
}

内容的提问来源于stack exchange,提问作者Rudransh Singh Mahra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 18:06:09