Flutter Firebase Auth底部导航切换页面闪烁问题及解决
解决Flutter Firebase Auth底部导航栏切换时登录页闪烁问题
问题描述
应用底部导航栏包含4个选项:Home无需登录即可访问,Profile、Messages、Listings需登录后使用。最初为每个需登录页面单独通过StreamBuilder监听Firebase Auth的authStateChanges流,已登录显示实际页面,未登录显示登录页。功能正常,但已登录用户切换导航标签时,会短暂显示登录页再加载实际页面,出现恼人的闪烁现象。
尝试过延迟100ms读取数据、用PageView实现切换动画,但延迟无效,PageView跨页切换动画体验极差。
问题原因
最初的实现中,每个需登录页面(如AuthProfile)都单独创建StreamBuilder并订阅authStateChanges流。切换导航标签时,对应的AuthXXX组件重新构建,StreamBuilder重新订阅流,初始阶段snapshot未获取到已登录用户数据,会先返回登录页组件,等流发出用户数据后才切换到实际页面,导致闪烁。
初始实现代码
单个需登录页面的封装(以AuthProfile为例)
class AuthProfile extends StatelessWidget { AuthProfile({Key? key}) : super(key: key); final Stream<User?> authInstance = FirebaseAuth.instance.authStateChanges(); @override Widget build(BuildContext context) { return StreamBuilder<User?>( stream: authInstance, builder: (context, snapshot) { if (snapshot.hasData) { return const Profile(); } else { return const ProfileLogin(); } }); } }
Main页面初始代码
void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarBrightness: Brightness.light, statusBarIconBrightness: Brightness.light, systemNavigationBarColor: Colors.white, systemNavigationBarIconBrightness: Brightness.light)); return const MaterialApp( home: Main(), ); } } class Main extends StatefulWidget { const Main({super.key}); @override State<Main> createState() => _Main(); } class _Main extends State<Main> { int _selectedIndex = 0; final PageController _pageController = PageController(initialPage: 0); @override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: false, body: PageView( controller: _pageController, physics: const NeverScrollableScrollPhysics(), children: [ const Home(), AuthListings(), AuthMessages(), AuthProfile(), ]), bottomNavigationBar: BottomNavigationBar( iconSize: 30, type: BottomNavigationBarType.fixed, backgroundColor: Colors.white, items: const <BottomNavigationBarItem>[ BottomNavigationBarItem( icon: Icon(Icons.home_rounded), label: 'Home', ), BottomNavigationBarItem( icon: Icon(Icons.insert_chart_rounded), label: 'Listings', ), BottomNavigationBarItem( icon: Icon(Icons.mark_as_unread_rounded), label: 'Messages', ), BottomNavigationBarItem( icon: Icon(Icons.account_circle_rounded), label: 'Profile') ], currentIndex: _selectedIndex, selectedItemColor: Colors.purple, unselectedItemColor: Colors.black, showUnselectedLabels: false, showSelectedLabels: false, onTap: _onTappedBar, ), ); } void _onTappedBar(int value) { setState(() { _selectedIndex = value; }); _pageController.jumpToPage(value); } }
修复后代码
void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarBrightness: Brightness.light, statusBarIconBrightness: Brightness.light, systemNavigationBarColor: Colors.white, systemNavigationBarIconBrightness: Brightness.light)); return const MaterialApp( home: Main(), ); } } class Main extends StatefulWidget { const Main({super.key}); @override State<Main> createState() => _Main(); } class _Main extends State<Main> { int _selectedIndex = 0; static final List<Widget> _loggedIn = <Widget>[ const Home(), const Listings(), const Messages(), const Profile(), ]; static final List<Widget> _loggedOut = <Widget>[ const Home(), const ListingsLogin(), const MessagesLogin(), const ProfileLogin(), ]; @override Widget build(BuildContext context) { final Stream<User?> authInstance = FirebaseAuth.instance.authStateChanges(); return Scaffold( resizeToAvoidBottomInset: false, body: Center( child: StreamBuilder<User?>( stream: authInstance, builder: (context, snapshot) { if (snapshot.hasData) { return _loggedIn.elementAt(_selectedIndex); } else { return _loggedOut.elementAt(_selectedIndex); } }), ), bottomNavigationBar: BottomNavigationBar( iconSize: 30, type: BottomNavigationBarType.fixed, backgroundColor: Colors.white, items: const <BottomNavigationBarItem>[ BottomNavigationBarItem( icon: Icon(Icons.home_rounded), label: 'Home', ), BottomNavigationBarItem( icon: Icon(Icons.insert_chart_rounded), label: 'Listings', ), BottomNavigationBarItem( icon: Icon(Icons.mark_as_unread_rounded), label: 'Messages', ), BottomNavigationBarItem( icon: Icon(Icons.account_circle_rounded), label: 'Profile') ], currentIndex: _selectedIndex, selectedItemColor: Colors.purple, unselectedItemColor: Colors.black, showUnselectedLabels: false, showSelectedLabels: false, onTap: _onTappedBar, ), ); } void _onTappedBar(int value) { setState(() { _selectedIndex = value; }); } }
修复说明
- 将
StreamBuilder提升到Main页面的body中,全局监听auth状态,避免每个页面重复订阅流 - 维护
_loggedIn和_loggedOut两个页面列表,根据auth状态直接切换整个列表 - 切换导航标签时,仅更新选中索引并从对应列表中取出页面,不会触发流的重新订阅,彻底解决闪烁问题
内容的提问来源于stack exchange,提问作者user17401443
相关产品推荐
相关产品推荐

