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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 14:32:21