Flutter中Firebase初始化与网络重连后导航失效问题排查
Flutter无网启动后联网重试时导航失效问题
我用Flutter开发一款依赖Firebase数据库的移动应用,仅能在联网状态下运行。在main函数中初始化Firebase,用户无网启动时会弹出提示对话框,联网后重新执行初始化与导航流程。正常联网启动时导航正常,但无网启动后联网重试的场景下,尽管所有函数调用都已正常执行(日志已确认),导航却无法生效。
启动页代码
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../View Models/Splash View Model.dart'; import 'Signup Screen.dart'; // 替换为实际注册页路径 class SplashScreen extends StatelessWidget { const SplashScreen({super.key}); @override Widget build(BuildContext context) { final viewModel = Provider.of<SplashViewModel>(context, listen: false); WidgetsBinding.instance.addPostFrameCallback((_) { _initializeApp(context, viewModel ,false); }); return Scaffold( body: Container( decoration: const BoxDecoration( image: DecorationImage( image: AssetImage('assets/drawables_png/splash_bg.png'), fit: BoxFit.cover, ), ), child: Column( children: [ Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( 'assets/drawables_png/splash_ic.png', width: 150, height: 150, ), const SizedBox(height: 20), const Text( 'EASY FOOD JOURNAL', style: TextStyle( fontFamily: 'Viga', fontSize: 24, color: Colors.white, ), ), const SizedBox(height: 10), const Text( 'Effortless Tracking for Delicious Living!', style: TextStyle( fontFamily: 'Outfit', fontSize: 16, color: Colors.white, ), ), const SizedBox(height: 40), ], ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 60.0, vertical: 30), child: Consumer<SplashViewModel>( builder: (context, viewModel, child) { return LinearProgressIndicator( value: viewModel.progress, backgroundColor: const Color(0xff29ffffff), valueColor: const AlwaysStoppedAnimation<Color>(Colors.white), ); }, ), ), ], ), ), ); } void _initializeApp(BuildContext context, SplashViewModel viewModel , bool callFirebase) async { print("finally here011"); bool connected = await viewModel.checkInternetConnection(); if (connected) { bool firebaseInitialized = await viewModel.initializeFirebase(callFirebase); if (firebaseInitialized) { print("finally here211"); await viewModel.startProgress(); print("finally here311"); _navigateBasedOnLoginStatus(context, viewModel); print("finally here411"); } else { _showNoConnectionDialog(context, viewModel); } } else { _showNoConnectionDialog(context, viewModel); } } void _showNoConnectionDialog(BuildContext context, SplashViewModel viewModel) { showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return AlertDialog( title: const Text("No Internet Connection"), content: const Text("Please connect to the internet to continue."), actions: [ TextButton( child: const Text("Retry"), onPressed: () { Navigator.of(context).pop(); _initializeApp(context, viewModel , true); // 重试初始化 }, ), ], ); }, ); } void _navigateBasedOnLoginStatus(BuildContext context, SplashViewModel viewModel) async { bool isLoggedIn = await viewModel.isUserLoggedIn(); // 使用addPostFrameCallback确保上下文有效 WidgetsBinding.instance.addPostFrameCallback((_) { if (isLoggedIn) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const SignupScreen()), // 登录状态下跳转首页 ); } else { Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const SignupScreen()), // 未登录跳转注册页 ); } }); } }
ViewModel代码
import 'dart:async'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/cupertino.dart'; import '../Utils/SharedPrefs.dart'; class SplashViewModel with ChangeNotifier { double _progress = 0.0; Timer? _timer; double get progress => _progress; /// 单独初始化Firebase,控制调用时机 Future<bool> initializeFirebase( bool callFirebase) async { try { if(callFirebase){ if (Firebase.apps.isEmpty) { await Firebase.initializeApp(); } } return true; } catch (e) { print("Firebase initialization failed: $e"); return false; } } /// 启动进度条动画 Future<void> startProgress() async { _resetProgress(); // 启动进度条动画 _timer = Timer.periodic(const Duration(milliseconds: 50), (timer) { if (_progress < 1.0) { _progress += 0.02; // 每次增加2%进度 notifyListeners(); } else { timer.cancel(); } }); // 为动画一致性,2秒后完成进度 if (Firebase.apps.length != 0) { await Future.delayed(const Duration(seconds: 2)); _progress = 1.0; notifyListeners(); _timer?.cancel(); } } void _resetProgress() { _progress = 0.0; notifyListeners(); _timer?.cancel(); } Future<bool> checkInternetConnection() async { var connectivityResult = await Connectivity().checkConnectivity(); return connectivityResult != ConnectivityResult.none; } Future<bool> isUserLoggedIn() async { User? currentUser = FirebaseAuth.instance.currentUser; if (currentUser == null) return false; bool onboardingComplete = await SharedPreferencesUtil.getValue<bool>('onboarding_complete', false); return onboardingComplete; } @override void dispose() { _timer?.cancel(); super.dispose(); } }
问题原因与修复方案
核心原因
重试时导航失效的关键问题是上下文使用错误:弹窗的builder方法会生成新的上下文,你在弹窗的onPressed中传入的是弹窗上下文而非SplashScreen的根上下文,导致Navigator.pushReplacement操作的是弹窗的导航栈,而非应用的根导航栈,因此无法触发页面跳转。
修复步骤
1. 保存并使用正确的上下文
修改_showNoConnectionDialog方法,提前保存SplashScreen的上下文,避免使用弹窗上下文:
void _showNoConnectionDialog(BuildContext context, SplashViewModel viewModel) { final rootContext = context; // 保存SplashScreen的根上下文 showDialog( context: context, barrierDismissible: false, builder: (BuildContext dialogContext) { // 这里是弹窗的上下文 return AlertDialog( title: const Text("无网络连接"), content: const Text("请连接网络后继续。"), actions: [ TextButton( child: const Text("重试"), onPressed: () { Navigator.of(dialogContext).pop(); _initializeApp(rootContext, viewModel, true); // 使用根上下文 }, ), ], ); }, ); }
2. 简化导航逻辑
移除_navigateBasedOnLoginStatus中不必要的WidgetsBinding.instance.addPostFrameCallback,直接使用有效上下文导航:
void _navigateBasedOnLoginStatus(BuildContext context, SplashViewModel viewModel) async { bool isLoggedIn = await viewModel.isUserLoggedIn(); if (isLoggedIn) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const SignupScreen()), // 替换为实际首页 ); } else { Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const SignupScreen()), // 替换为实际注册页 ); } }
3. 优化进度动画逻辑(可选)
避免定时器与固定延迟的冲突,让进度动画自然完成:
Future<void> startProgress() async { _resetProgress(); _timer = Timer.periodic(const Duration(milliseconds: 50), (timer) { if (_progress < 1.0) { _progress += 0.02; notifyListeners(); } else { timer.cancel(); } }); // 等待进度完全加载完成 await Future.doWhile(() async { await Future.delayed(const Duration(milliseconds: 50)); return _progress < 1.0; }); }
内容的提问来源于stack exchange,提问作者Shaheer Ilyas
相关产品推荐
相关产品推荐

