Flutter点击Retry按钮触发组件已卸载上下文异常问题求助
问题描述
启动页检查用户网络状态,无网时展示无网络页面并提供Retry按钮,点击按钮时触发以下异常:
Unhandled Exception: This widget has been unmounted, so the State no longer has a context (and should be considered defunct)
E/flutter (25542): Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active.
相关代码
启动页代码
class SplashScreen extends StatefulWidget { const SplashScreen({Key? key}) : super(key: key); @override State<SplashScreen> createState() => _SplashScreenState(); } class _SplashScreenState extends State<SplashScreen> { Future<bool> isLoggedIn() async { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString("token"); print("Token obtained: $token"); return token != null; } @override void initState() { _init(); super.initState(); } Future<void> _init() async { bool isConnected = await NetworkHelper.checkInternet(); if (!isConnected) { if (mounted) { Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => CheckConnection( onRetry: _init, )), ); } } else { await Future.delayed(const Duration(seconds: 3), () async { final isTokenValid = await isLoggedIn(); if (isTokenValid) { if (mounted) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => BottomNav()), ); } } else { if (mounted) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => Signup()), ); } } }); } } @override Widget build(BuildContext context) { return Scaffold( body: Container( color: Colors.white, child: FlutterLogo(size: MediaQuery.of(context).size.height), ), ); } }
无网络页面代码
class CheckConnection extends StatelessWidget { final VoidCallback onRetry; const CheckConnection({Key? key, required this.onRetry}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset("assets/image/no_internet.jpg"), const SizedBox(height: 20), ElevatedButton( onPressed: onRetry, child: const Text("Retry"), ), ], ), ); } }
问题原因
当SplashScreen通过Navigator.pushReplacement跳转到CheckConnection后,SplashScreen的State已被销毁(unmounted),但CheckConnection仍持有SplashScreen中_init方法的引用。点击Retry按钮调用_init时,方法内的mounted检查针对的是已失效的SplashScreenState,其context无法再用于导航操作,从而触发异常。
解决方案
将网络检查和页面导航逻辑迁移到CheckConnection内部,让它独立处理重试逻辑,不再依赖已销毁的SplashScreenState。修改后的代码如下:
修改后的无网络页面(转为StatefulWidget)
class CheckConnection extends StatefulWidget { const CheckConnection({Key? key}) : super(key: key); @override State<CheckConnection> createState() => _CheckConnectionState(); } class _CheckConnectionState extends State<CheckConnection> { Future<bool> isLoggedIn() async { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString("token"); print("Token obtained: $token"); return token != null; } Future<void> _checkAndNavigate() async { bool isConnected = await NetworkHelper.checkInternet(); if (isConnected) { final isTokenValid = await isLoggedIn(); if (mounted) { if (isTokenValid) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => BottomNav()), ); } else { Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => Signup()), ); } } } } @override Widget build(BuildContext context) { return Scaffold( body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset("assets/image/no_internet.jpg"), const SizedBox(height: 20), ElevatedButton( onPressed: _checkAndNavigate, child: const Text("Retry"), ), ], ), ); } }
修改后的启动页代码
class SplashScreen extends StatefulWidget { const SplashScreen({Key? key}) : super(key: key); @override State<SplashScreen> createState() => _SplashScreenState(); } class _SplashScreenState extends State<SplashScreen> { Future<bool> isLoggedIn() async { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString("token"); print("Token obtained: $token"); return token != null; } @override void initState() { _init(); super.initState(); } Future<void> _init() async { bool isConnected = await NetworkHelper.checkInternet(); if (!isConnected) { if (mounted) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => const CheckConnection()), ); } } else { await Future.delayed(const Duration(seconds: 3), () async { final isTokenValid = await isLoggedIn(); if (mounted) { if (isTokenValid) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => BottomNav()), ); } else { Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => Signup()), ); } } }); } } @override Widget build(BuildContext context) { return Scaffold( body: Container( color: Colors.white, child: FlutterLogo(size: MediaQuery.of(context).size.height), ), ); } }
说明
- 将
CheckConnection改为StatefulWidget,内部实现_checkAndNavigate方法处理网络检查和导航,确保使用当前页面的有效context。 - 启动页跳转到
CheckConnection时不再传递回调,避免引用已销毁的State方法。 - 所有导航操作前保留
mounted检查,防止页面在异步操作完成前被销毁引发异常。
内容的提问来源于stack exchange,提问作者Mansoor Malik
相关产品推荐
相关产品推荐

