Flutter状态切换导致子Widget被dispose时如何安全弹出对话框?
解决方案:使用全局NavigatorKey获取顶层稳定上下文
该方案完全符合你的两个诉求,无需调整现有组件拆分结构,也不需要给子组件传递任何回调,仅需3处小改动即可解决问题:
- 定义全局NavigatorKey,绑定到MaterialApp上,该Key对应的上下文属于应用顶层Navigator层级,不会随页面内子组件切换销毁而失效。
- 改造弹窗方法,优先使用顶层稳定上下文弹出弹窗,避免依赖子组件的临时上下文。
修改后的完整可运行代码:
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // 新增:全局NavigatorKey,绑定到MaterialApp final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); void main() { runApp(const MyApp()); } void showAlertDialog({ BuildContext? context, required String title, required String body, required bool showCancelOption, Function? onConfirm, }) { // 优先使用顶层稳定上下文,不受子组件销毁影响 final safeContext = navigatorKey.currentContext ?? context!; showDialog<void>( context: safeContext, barrierDismissible: false, builder: (BuildContext context) { List<Widget> actions = [ TextButton( child: const Text("OK"), onPressed: () { Navigator.pop(context); if (onConfirm != null) { onConfirm(); } }, ), ]; if (showCancelOption) { actions.add( TextButton( child: const Text("Cancel"), onPressed: () => Navigator.pop(context), ), ); } return AlertDialog( title: Text(title), content: Text(body), actions: actions, ); }, ); } enum MyState { one, transition, two, } class MyStateModel extends ChangeNotifier { MyState _state = MyState.one; MyState get state => _state; Future<bool> transitionState() async { var newState = _state == MyState.one ? MyState.two : MyState.one; _state = MyState.transition; notifyListeners(); await Future.delayed(const Duration(milliseconds: 500)); // 模拟网络请求 _state = newState; notifyListeners(); return true; } } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return ChangeNotifierProvider<MyStateModel>( create: (context) => MyStateModel(), child: MaterialApp( title: 'Flutter Demo', // 绑定全局NavigatorKey navigatorKey: navigatorKey, theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(), ), ); } } class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { var model = Provider.of<MyStateModel>(context); Widget body; // 原有简洁的状态分支逻辑完全保留,无需传递任何回调 switch (model.state) { case MyState.one: body = const StateOneWidget(); break; case MyState.transition: body = const StateTransitionWidget(); break; case MyState.two: body = const StateTwoWidget(); break; } return Scaffold( body: body, ); } } class StateOneWidget extends StatelessWidget { const StateOneWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Center( child: ElevatedButton( child: const Text("切换到状态2"), onPressed: () { showAlertDialog( context: context, title: "确认切换到状态2?", body: "该操作会执行状态切换逻辑", showCancelOption: true, onConfirm: () async { var model = Provider.of<MyStateModel>(context, listen: false); var success = await model.transitionState(); if (success) { showAlertDialog( context: context, title: "切换成功", body: "已进入状态2", showCancelOption: false, ); } }, ); }, ), ); } } class StateTwoWidget extends StatelessWidget { const StateTwoWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Center( child: ElevatedButton( child: const Text("切换到状态1"), onPressed: () { showAlertDialog( context: context, title: "确认切换到状态1?", body: "该操作会执行状态切换逻辑", showCancelOption: true, onConfirm: () async { var model = Provider.of<MyStateModel>(context, listen: false); var success = await model.transitionState(); if (success) { showAlertDialog( context: context, title: "切换成功", body: "已进入状态1", showCancelOption: false, ); } }, ); }, ), ); } } class StateTransitionWidget extends StatelessWidget { const StateTransitionWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const Center( child: CircularProgressIndicator(), ); } }
方案说明
- 完全保留了你按状态拆分无状态子组件的代码结构,所有子组件的逻辑不需要做大的调整
- 根组件不需要给子组件传递任何回调参数,build逻辑保持简洁干净
- 顶层Navigator上下文永远稳定存在,不会因为页面内子组件的切换、销毁而失效,从根源上避免了context失效的报错。
内容的提问来源于stack exchange,提问作者Scorb
相关产品推荐
相关产品推荐

