使用不包含 Navigator 的上下文请求的 Navigator 操作

Posted

技术标签:

【中文标题】使用不包含 Navigator 的上下文请求的 Navigator 操作【英文标题】:Navigator operation requested with a context that does not include a Navigator 【发布时间】:2017-10-15 17:05:08 【问题描述】:

我正在尝试在 onTap 中启动新屏幕,但出现以下错误:

使用不包含 导航器。

我用来导航的代码是:

onTap: ()  Navigator.of(context).pushNamed('/settings'); ,

我在我的应用中设置了如下路线:

routes: <String, WidgetBuilder>
    '/settings': (BuildContext context) => new SettingsPage(),
,

我尝试使用股票示例应用程序复制代码。我查看了 Navigator 和 Route 文档,但无法弄清楚如何使上下文包含 Navigator。 onTap 中使用的context 是从传递给构建方法的参数中引用的:

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 

SettingsPage 是一个类如下:

class SettingsPage extends Navigator 

Widget buildAppBar(BuildContext context) 
  return new AppBar(
    title: const Text('Settings')
  );


@override
Widget build(BuildContext context) 
  return new Scaffold(
    appBar: buildAppBar(context),
  );
 

【问题讨论】:

【参考方案1】:

TLDR:将需要访问Navigator 的小部件包装成Builder 或将该子树提取到一个类中。并使用新的BuildContext 访问Navigator


此错误与目的地无关。发生这种情况是因为您使用了不包含 Navigator 实例作为父级的 context

那我该如何创建一个 Navigator 实例呢?

这通常通过在您的小部件树中插入MaterialAppWidgetApp 来完成。虽然您可以直接使用Navigator 手动完成,但不太推荐。然后,此类小部件的所有子级都可以使用Navigator.of(context) 访问NavigatorState

等等,我已经有MaterialApp/WidgetApp了!

很可能是这种情况。但是,当您使用作为 MaterialApp/WidgetApp 的父级的 context 时,仍然会发生此错误。

发生这种情况是因为当您执行Navigator.of(context) 时,它将从与使用的context 关联的小部件开始。然后在小部件树中向上移动,直到找到Navigator 或没有更多小部件。

在第一种情况下,一切都很好。在第二个中,它抛出一个

使用不包含导航器的上下文请求导航器操作。

那么,我该如何解决呢?

首先,让我们重现这个错误:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      home: Center(
        child: RaisedButton(
          child: Text("Foo"),
          onPressed: () => Navigator.pushNamed(context, "/"),
        ),
      ),
    );
  

此示例创建一个按钮,该按钮尝试在单击时转到“/”,但会引发异常。

注意这里的

  onPressed: () => Navigator.pushNamed(context, "/"),

我们使用context 传递给buildMyApp

问题是,MyApp 实际上是MaterialApp 的父级。因为它是实例化MaterialApp 的小部件!因此MyAppBuildContext 没有MaterialApp 作为父级!

为了解决这个问题,我们需要使用不同的context

在这种情况下,最简单的解决方案是引入一个新小部件作为MaterialApp 的子小部件。然后使用该小部件的上下文进行Navigator 调用。

有几种方法可以实现这一点。您可以将home 提取到自定义类中:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      home: MyHome()
    );
  


class MyHome extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Center(
        child: RaisedButton(
          child: Text("Foo"),
          onPressed: () => Navigator.pushNamed(context, "/"),
        ),
      );
  

或者你可以使用Builder

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      home: Builder(
        builder: (context) => Center(
              child: RaisedButton(
                child: Text("Foo"),
                onPressed: () => Navigator.pushNamed(context, "/"),
              ),
            ),
      ),
    );
  

【讨论】:

该解决方案看起来不错。但是如果我需要使用 initialRoute 而不是 home 呢? @davidweng 你能解释一下关于initialRoute的问题吗?好奇。 谢谢,我没有看到任何解释这么多的答案..非常感谢.. 终于有解释了。 :) 谢谢! @GrumpyRodriguez 这个答案假设您想在家里使用一个小部件。但是这个解决方案并没有扩展到你使用路由器的情况,因此需要为你拥有的每条路由实现某种基本小部件。例如,如果您需要在需要使用 Navigator 的公共小部件中初始化侦听器,这将不起作用,因为所有路由都没有公共小部件。小部件被复制,您的监听器也是如此。【参考方案2】:

大家好,我也有同样的问题。这发生在我身上。我找到的解决方案非常简单。只有我所做的只是一个简单的代码:

void main() 
  runApp(MaterialApp(
    home: YOURAPP() ,
    ),
  );

希望对你有用。

【讨论】:

这是最简单的解决方案。重点!【参考方案3】:

确保您当前的父小部件与 MaterialApp 的级别不同

错误的方式

class HomeScreen extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          centerTitle: true,
          title: Text('Title'),
        ),
        body: Center(
            child: Padding(
          padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
          child: RaisedButton(
              onPressed: () 
                //wrong way: use context in same level tree with MaterialApp
                Navigator.push(context,
                    MaterialPageRoute(builder: (context) => ScanScreen()));
              ,
              child: const Text('SCAN')),
        )),
      ),
    );
  

正确的方式

void main() => runApp(MaterialApp(
      title: "App",
      home: HomeScreen(),
    ));

class HomeScreen extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text('Title'),
      ),
      body: Center(
          child: Padding(
        padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
        child: RaisedButton(
            onPressed: () 
            //right way: use context in below level tree with MaterialApp
              Navigator.push(context,
                  MaterialPageRoute(builder: (context) => ScanScreen()));
            ,
            child: const Text('SCAN')),
      )),
    );
  

【讨论】:

最佳解释 如何传递多个参数并在另一个屏幕上获取/使用它们?请建议。谢谢 @Kamlesh 只需通过您的小部件(类)的构造函数传递它 感谢您的快速回复。您能否通过添加不同类型的 2 个参数来更新您的答案?非常感谢。【参考方案4】:

我在 Flutter 应用中设置了这个简单的路由示例:

import 'package:flutter/material.dart';

void main() 
  runApp(new MyApp());


class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return new MaterialApp(
      title: 'Flutter Demo',
      home: new MyHomePage(),
      routes: <String, WidgetBuilder>
        '/settings': (BuildContext context) => new SettingsPage(),
      ,
    );
  


class MyHomePage extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('TestProject'),
      ),
      body: new Center(
        child: new FlatButton(
          child: const Text('Go to Settings'),
          onPressed: () => Navigator.of(context).pushNamed('/settings')
        )
      )
    );
  


class SettingsPage extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return new Scaffold(
        appBar: new AppBar(
          title: new Text('SettingsPage'),
        ),
        body: new Center(
            child: new Text('Settings')
        )
    );
  

注意,SettingsPage 扩展 StatelessWidget 而不是 Navigator。我无法重现您的错误。

此示例对您构建应用程序有帮助吗?让我知道是否可以帮助您解决其他问题。

【讨论】:

是的,这有帮助!我在 MaterialApp 的构造函数中设置了home: new Scaffold(appBar: new AppBar(title: new Text('GroupUp'),),,而不是创建另一个小部件。 (至少我认为这是问题所在) 你的 onTap 比哪里来的? 也许你遇到了github.com/flutter/flutter/issues/15919 如何传递多个参数并在另一个屏幕上获取/使用它们?请建议。谢谢。【参考方案5】:

就像使用脚手架一样,您可以使用GlobalKey。它不需要上下文。

final _navKey = GlobalKey<NavigatorState>();

void _navigateToLogin() 
  _navKey.currentState.popUntil((r) => r.isFirst);
  _navKey.currentState.pushReplacementNamed(LoginRoute.name);


@override
Widget build(BuildContext context) 
  return MaterialApp(
    navigatorKey: _navKey,
    ...
  );

【讨论】:

这应该是公认的答案。如果您想在 MaterialApp 之外从 MyApp 推送新路线,那么这就是方式.. 我遇到了一个错误:If no route is provided using home, routes, onGenerateRoute, or onUnknownRoute, a non-null callback for the builder property must be provided, and the other navigator-related properties, navigatorKey, initialRoute, and navigatorObservers, must have their initial values (null, null, and the empty list, respectively). 如何传递多个参数并在另一个屏幕上获取/使用它们?请建议。谢谢【参考方案6】:

你应该在 main.dart 中重写你的代码 来自:

void main() => runApp(MyApp());

void main() 
  runApp(MaterialApp(
  title: 'Your title',
  home: MyApp(),));

关键是让 home 属性成为您的第一页 这对我有用,我希望它对将来的人有所帮助

【讨论】:

【参考方案7】:

完整且经过测试的解决方案:

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:my-app/view/main-view.dart';

class SplashView extends StatelessWidget 

  @override
  Widget build(BuildContext context) 
    return new MaterialApp(
        home: Builder(
          builder: (context) => new _SplashContent(),
        ),
        routes: <String, WidgetBuilder>
          '/main': (BuildContext context) => new MainView()
    );
  


class _SplashContent extends StatefulWidget

  @override
  _SplashContentState createState() => new _SplashContentState();


class _SplashContentState extends State<_SplashContent>
    with SingleTickerProviderStateMixin 

  var _iconAnimationController;
  var _iconAnimation;

  startTimeout() async 
    var duration = const Duration(seconds: 3);
    return new Timer(duration, handleTimeout);
  

  void handleTimeout() 
    Navigator.pushReplacementNamed(context, "/main");
  

  @override
  void initState() 
    super.initState();

    _iconAnimationController = new AnimationController(
        vsync: this, duration: new Duration(milliseconds: 2000));

    _iconAnimation = new CurvedAnimation(
        parent: _iconAnimationController, curve: Curves.easeIn);
    _iconAnimation.addListener(() => this.setState(() ));

    _iconAnimationController.forward();

    startTimeout();
  

  @override
  Widget build(BuildContext context) 
    return new Center(
        child: new Image(
          image: new AssetImage("images/logo.png"),
          width: _iconAnimation.value * 100,
          height: _iconAnimation.value * 100,
        )
    );
  

【讨论】:

如何传递多个参数并在另一个屏幕上获取/使用它们?请建议。谢谢【参考方案8】:

我遇到了同样的问题,并通过从 MaterialApp 中删除 home 并改用 initialRoute 来解决。

return MaterialApp(
      debugShowCheckedModeBanner: false,
      initialRoute: '/',
      routes: 
        '/': (context) => MyApp(),
        '/settings': (context) => SettingsPage(),
      ,
    );

onTap: () => 
               Navigator.pushNamed(context, "/settings")
                ,

【讨论】:

【参考方案9】:

根据this comment,如果您的导航器位于 Material 上下文导航器中,则推送将给出此错误。如果您创建一个新的小部件并将其分配给材料应用程序主页导航器将起作用。

这行不通

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return new MaterialApp(
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text("Title"),
        ),
        body: new Center(child: new Text("Click Me")),
        floatingActionButton: new FloatingActionButton(
          child: new Icon(Icons.add),
          backgroundColor: Colors.orange,
          onPressed: () 
            print("Clicked");
            Navigator.push(
              context,
              new MaterialPageRoute(builder: (context) => new AddTaskScreen()),
            );
          ,
        ),
      ),
    );
  

这会起作用

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return new MaterialApp(
        home: new HomeScreen());
  


class HomeScreen extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Title"),
      ),
      body: new Center(child: new Text("Click Me")),
      floatingActionButton: new FloatingActionButton(
        child: new Icon(Icons.add),
        backgroundColor: Colors.orange,
        onPressed: () 
          print("Clicked");
          Navigator.push(
            context,
            new MaterialPageRoute(builder: (context) => new AddTaskScreen()),
          );
        ,
      ),
    );
  

【讨论】:

如何传递多个参数并在另一个屏幕上获取/使用它们?请建议。谢谢【参考方案10】:

发生这种情况是因为尝试导航的小部件上的上下文仍在使用材质小部件。

解决方案的简短答案是:

提取您的小部件

具有到新类的导航,因此在调用导航时具有不同的上下文

【讨论】:

【参考方案11】:

更改您的主要功能示例:

`void main() 
  runApp(MaterialApp(
  title: 'Your title',
  home: MyApp(),));`

【讨论】:

【参考方案12】:

当您的屏幕未从其他屏幕导航时,您最初无法访问导航器,因为它尚未实例化。因此在这种情况下,使用构建器包装您的小部件并从那里提取上下文。这对我有用.

builder: (context) => Center(
              child: RaisedButton(
                child: Text("Foo"),
                onPressed: () => Navigator.pushNamed(context, "/"),
              ),

【讨论】:

【参考方案13】:

你可以使用这个插件 https://pub.dev/packages/get/versions/2.0.2

在MaterialApp中赋值属性navigatorKey:Get.key,

MaterialApp(
      navigatorKey: Get.key,
      initialRoute: "/",
     );

您可以访问Get.toNamed("Your route name");

【讨论】:

以上是关于使用不包含 Navigator 的上下文请求的 Navigator 操作的主要内容,如果未能解决你的问题,请参考以下文章

Dart / Flutter:在没有上下文的页面之间导航(使用 Navigator)(所以没有 Navigator.push?)

Navigator.share 仅在 iOS 中工作一次,第二次单击会引发错误“用户代理不允许请求...”

Navigator 对象,Screen 对象。

处理 django 包含模板标签中的请求

JavaScript Window Navigator

Flutter - 从 AppBar 返回上一页不会刷新页面,使用 Navigator.pop(context)