【问题标题】:showDialog from root widget来自根小部件的 showDialog
【发布时间】:2019-04-17 02:03:38
【问题描述】:

我想从根小部件(创建 MaterialApp 的那个)显示一个对话框我有一个 NavigatorState 实例,但 showDialog 需要返回 Navigator.of(context) 的上下文。

看起来我需要从路由中提供上下文,但我不能这样做,因为根小部件没有它。

编辑:我找到了一种解决方法:我可以推送仅存在于 showDialog 的假路由,然后在对话框完成时弹出该路由。不漂亮但有效。

【问题讨论】:

    标签: flutter


    【解决方案1】:

    我使用navigatorKey.currentState.overlay.context 解决了这个问题。示例如下:

    class GlobalDialogApp extends StatefulWidget {
      @override
      _GlobalDialogAppState createState() => _GlobalDialogAppState();
    }
    
    class _GlobalDialogAppState extends State<GlobalDialogApp> {
      final navigatorKey = GlobalKey<NavigatorState>();
    
      void show() {
        final context = navigatorKey.currentState.overlay.context;
        final dialog = AlertDialog(
          content: Text('Test'),
        );
        showDialog(context: context, builder: (x) => dialog);
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          navigatorKey: navigatorKey,
          home: Scaffold(
            body: Center(
              child: RaisedButton(
                child: Text('Show alert'),
                onPressed: show,
              ),
            ),
          ),
        );
      }
    }
    

    【讨论】:

    • @stuckedoverflow 我查看了 Navigator 的源代码,发现它包含 Overlay 小部件。
    • 谢谢,navigatorKey.currentState.overlay.context; 成功了。
    • 它不再适用于 Flutter 1.7.X 版,我已经发布了一个更一致的工作解决方案。
    • @OliverDixon 仍然对我有用。另外,你的flutter版本好像很老了,1.7.8是一年前发布的。
    • @szotp Flutter 1.7.0 版是几个月前发布的。它们的版本号不一致。 flutter.dev/docs/development/tools/sdk/releases
    【解决方案2】:

    tl;dr:如果您想从根窗口小部件调用showDialog,请将您的代码压缩到另一个窗口小部件(例如StatelessWidget),然后在那里调用showDialog

    无论如何,在下面我将假设您遇到了这个问题:

    flutter: No MaterialLocalizations found. 
    flutter: MyApp widgets require MaterialLocalizations to be provided by a Localizations widget ancestor. 
    flutter: Localizations are used to generate many different messages, labels,and abbreviations which are used by the material library.
    

    如前所述,showDialog 只能在 BuildContext 中调用,其祖先有 MaterialApp。因此,如果您有这样的结构,您不能直接调用showDialog

    - MaterialApp
      - Scaffold
        - Button // call show Dialog here
    

    在代码示例中,这将导致如下代码,抛出上面给出的错误:

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData(),
          home: Scaffold(
            body: Center(
              child: RaisedButton(
                  child: Text('Show dialog!'),
                  onPressed: () {
                    showDialog(
                        context: context,
                        builder: (BuildContext context) {
                          return Dialog(
                            child: Text('Dialog.'),
                          );
                        });
                  }),
            ),
          ),
        );
      }
    }
    

    要解决此错误,您可以创建一个新的Widget,它有自己的BuildContext。修改后的结构如下所示:

    - MaterialApp
      - Home
    
    - Home     // your own (Stateless)Widget
      - Button // call show Dialog here
    

    将代码示例修改为上面给出的结构,得到下面的代码 sn-p。 showDialog可以被调用而不抛出错误。

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData(),
          home: Home()
        );
      }
    }
    
    class Home extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: RaisedButton(
                child: Text('Show dialog!'),
                onPressed: () {
                  showDialog(
                      context: context,
                      builder: (BuildContext context) {
                        return Dialog(
                          child: Text('Dialog.'),
                        );
                      });
                }),
          ),
        );
      }
    }
    

    【讨论】:

    • 显然这会起作用,但我的用例是例如在收到通知时显示警报。它可以在应用程序的任何状态下发生,因此我没有特定页面来显示对话框. 也许如果有一个页面总是在层次结构中它可以工作,但我的应用程序目前不是这种情况。
    • @krzat 哦,抱歉,也许您想尝试使用 Overlay 小部件。如果您有NavigatorState,则显示它应该没有问题(example)。要创建与 showDialog 函数相同的效果,您可以将其设为全屏尺寸并为其设置不透明的背景色。
    • 也许navigator.overlay.context 可以解决问题,我会检查一下。
    • @Niklas 为什么您的第一个示例会引发异常?异常消息显示为To introduce a MaterialLocalizations, either use a MaterialApp at the root of your application to include them automatically, or ....,您在第一个示例中已经这样做了。你能解释一下为什么你的第二个例子有效,而不是第一个?
    • 第一个buildContext 不是MaterialApp 的子代。通过将代码移动到MaterialApp(在本例中为Home)的不同子小部件,我们更新了buildContext
    【解决方案3】:

    他们改变了导航覆盖的工作方式。 这对我们来说是可行的解决方案,因为已不再被接受。

    // If you want to use the context for anything.
    final context = navigatorKey.currentState.overlay.context;
    // How to insert the dialog into the display queue.
    navigatorKey.currentState.overlay.insert(anyDialog);
    

    【讨论】:

      【解决方案4】:

      由于showDialog 用于显示材质对话框,因此它只能用于在MaterialApp 小部件内显示对话框。它不能用于在其外部显示对话框。

      【讨论】:

      • 我引用了NavigatorState,所以我不明白为什么不应该这样做。
      【解决方案5】:

      如果您已经有一个context 对象,您可以通过以下方式获取根材质应用程序的上下文 final rootContext = context.findRootAncestorStateOfType&lt;NavigatorState&gt;().context

      并将其传递给showDialogshowModalBottomSheet 上下文参数。

      【讨论】:

        【解决方案6】:

        如果它对其他人有帮助,请将导航键注入对话框小部件,如下所示。

        class MyApp extends StatelessWidget {
          MyApp({Key key});
        
          final navigatorKey = GlobalKey<NavigatorState>();
        
          @override
          Widget build(BuildContext context) {
            return MaterialApp(
              navigatorKey: navigatorKey,
              onGenerateRoute: Router.generateRoute,
              // ...
              builder: (context, routeChild) {
                return Material(
                  child: InviteRequestModal(
                    navigatorKey: navigatorKey,
                    child: routeChild,
                  ),
                );
              },
            );
          }
        

        那么在需要modal的Widget中,就可以按照上面说的那样使用了。

        class InviteRequestModal extends StatelessWidget {
          final Widget child;
          final GlobalKey<NavigatorState> navigatorKey;
        
          InviteRequestModal({
            Key key,
            this.child,
            this.navigatorKey,
          }) : super(key: key);
        
          void _showInviteRequest(InviteRequest invite) {
            final context = navigatorKey.currentState.overlay.context;
        
            showDialog(
              context: context,
              builder: (_) {
                // Your dialog content
                return Container();
              }
            );
          }
        
          @override
          Widget build(BuildContext context) {
            return BlocListener<InviteContactsBloc, InviteContactsState>(
              listenWhen: (previous, current) => current is InviteRequestLoaded,
              listener: (_, state) {
                if (state is InviteRequestLoaded) {
                  _showInviteRequest(state.invite);
                }
              },
              child: child,
            );
          }
        }
        

        【讨论】:

          【解决方案7】:

          对于那些想了解如何在多个小部件/路由/文件场景中执行此操作的人,我将它与 InheritedWidgetBuildContext 上的扩展一起使用。

          main.dart

          import 'package:flutter/material.dart';
          import 'package:myapp/home_screen.dart';
          import 'package:myapp/app_navkey.dart';
          
          void main() => runApp(MyApp());
          
          class MyApp extends StatelessWidget {
            final navigatorKey = GlobalKey<NavigatorState>();
            @override
            Widget build(BuildContext context) {
              return AppNavKey(
                navigatorKey: navigatorKey,
                child: MaterialApp(
                  navigatorKey: navigatorKey,
                  theme: ThemeData(),
                  home: Scaffold(
                    body: HomeScreen(),
                  ),
                ),
              );
            }
          }
          

          home_screen.dart

          import 'package:myapp/extensions.dart';
          
          class HomeScreen extends StatelessWidget {
            @override
            Widget build(BuildContext context) {
              final overlayContext = context.navigationKey().currentState.overlay.context;
              return Center(
                child: TextButton(
                    child: Text('Show dialog!'),
                    onPressed: () {
                      showDialog(
                          context: overlayContext, // use app level navigation context overlay
                          builder: (BuildContext context) {
                            return Dialog(
                              child: Text('Dialog.'),
                            );
                          });
                    },
                  ),
                );
            }
          }
          

          app_navkey.dart

          import 'package:flutter/widgets.dart';
          
          class AppNavKey extends InheritedWidget {
            final Widget child;
            final GlobalKey<NavigatorState> navigatorKey;
          
            AppNavKey({
              Key key,
              @required this.child,
              @required this.navigatorKey,
            }) : super(key: key, child: child);
          
            static GlobalKey<NavigatorState> of(BuildContext context) {
              final ctx = context.dependOnInheritedWidgetOfExactType<AppNavKey>();
              if (ctx == null) throw Exception('Could not find ancestor of type AppNavProvider');
              return ctx.navigatorKey;
            }
          
            @override
            bool updateShouldNotify(covariant InheritedWidget oldWidget) => false;
          }
          

          extensions.dart

          import 'package:flutter/widgets.dart';
          import 'package:myapp/app_navkey.dart';
          
          extension SwitchTabContext on BuildContext {
            /// Get app level NavigatorState key.
            /// ```dart
            /// context.navigationKey();
            /// ```
            GlobalKey<NavigatorState> navigationKey() => AppNavKey.of(this);
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-12-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-12-28
            • 2019-10-11
            • 2019-08-31
            相关资源
            最近更新 更多