Flutter侧边栏Drawer仅能打开一次 再次打开显示圆形进度指示器
问题根因
- 冗余监听同一条数据流:页面Body和Drawer各使用了一个StreamBuilder监听
authBloc.currentUser用户流,Drawer是Scaffold的懒加载组件,首次打开时初始化,关闭后组件会被销毁。如果你的AuthBloc中currentUser没有使用可缓存最新值的可观察对象(比如RxDart的BehaviorSubject)实现,第二次打开Drawer重新监听流时,无法获取到之前已经发射过的用户数据,就会一直显示加载指示器。 - 数据没有复用:同一个页面内重复请求相同的用户数据,增加不必要的性能开销。
最优优化方案
直接复用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
相关产品推荐
相关产品推荐

