33

I have app with two screens, and I want to make push from 1st to second screen by pressing button.

Screen 1

import 'package:flutter/material.dart';
import './view/second_page.dart';

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

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new MainScreen();
  }
}

class MainScreen extends State<MyApp> {

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

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
        home: new Scaffold(
            appBar: new AppBar(
                title: new Text("Title")
            ),
            body: new Center(
                child: new FlatButton(child: new Text("Second page"),
                    onPressed: () {
                      Navigator.push(context,
                          new MaterialPageRoute(
                              builder: (context) => new SecondPage()))
                    }
                )
            )
        )
    );
  }
}

Screen 2

import 'package:flutter/material.dart';

class SecondPage extends StatefulWidget {

  @override
  State<StatefulWidget> createState() {
    return new SecondPageState();
  }
}


class SecondPageState extends State<SecondPage> {

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

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Title"),
      ),
      body: new Center(
        child: new Text("Some text"),
      ),
    );
  }
}

Push not happening and I got this

The following assertion was thrown while handling a gesture: Navigator operation requested with a context that does not include a Navigator. The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.

Another exception was thrown: Navigator operation requested with a context that does not include a Navigator.

What is wrong?

Community
  • 1
  • 1
moonvader
  • 19,761
  • 18
  • 67
  • 116

8 Answers8

47

Think of the widgets in Flutter as a tree, with the context pointing to whichever node is being built with the build function. In your case, you have

MainScreen    <------ context
  --> MaterialApp
   (--> Navigator built within MaterialApp)
      --> Scaffold
        --> App Bar
          --> ...
        --> Center
          --> FlatButton

So when you're using the context to find the Navigator, you're using a context for the MainScreen which isn't under the navigator.

You can either make a new Stateless or Stateful Widget subclass to contain your Center + FlatButton, as the build function within those will point at that level instead, or you can use a Builder and define the builder callback (which has a context pointing at the Builder) to return the Center + FlatButton.

rmtmckenzie
  • 37,718
  • 9
  • 112
  • 99
25

Just make the MaterialApp class in main method as this example

 void main() => runApp(MaterialApp(home: FooClass(),));

it works fine for me, I hope it will work with you

YahYa
  • 407
  • 4
  • 9
24

There are two main reasons why the route cannot be found.

1) The Route is defined below the context passed to Navigator.of(context) - scenario which @rmtmackenzie has explained

2) The Route is defined on the sibling branch e.g.

Root 

  -> Content (Routes e.g. Home/Profile/Basket/Search)

  -> Navigation (we want to dispatch from here)

If we want to dispatch a route from the Navigation widget, we have to know the reference to the NavigatorState. Having a global reference is expensive, especially when you intend to move widget around the tree. https://docs.flutter.io/flutter/widgets/GlobalKey-class.html. Use it only where there is no way to get it from Navigator.of(context).

To use a GlobalKey inside the MaterialApp define navigatorKey

final navigatorKey = GlobalKey<NavigatorState>();

Widget build(BuildContext context) => MaterialApp {
    navigatorKey: navigatorKey
    onGenerateRoute : .....
};

Now anywhere in the app where you pass the navigatorKey you can now invoke

navigatorKey.currentState.push(....);

Just posted about it https://medium.com/@swav.kulinski/flutter-navigating-off-the-charts-e118562a36a5

robotoaster
  • 3,082
  • 1
  • 24
  • 23
  • Using GlobalKey is relatively expensive and should be avoided if at all possible - while you _can_ use it, that doesn't mean you _should_ unless you have a specific use-case that necessitates it. And I'd argue that if you do need to pass it around, you can probably refactor your code to avoid it. You definitely shouldn't be passing it around! The whole mechanism of `Nativator.of(context)` is meant to avoid having to pass things around. – rmtmckenzie Jul 03 '18 at 17:04
  • 1
    Can you elaborate why passing reference is more expensive to traversing the tree. – robotoaster Jul 05 '18 at 10:13
  • Passing the reference is cheap - it's more the fact that GlobalKey itself is a heavy-weight solution... and anything named 'Global' is probably not great =D. Also, from the flutter docs: `Global keys are relatively expensive`. They can also have weird behaviour - see [this other question](https://stackoverflow.com/questions/49344086/why-flutter-globalkeys-currentstate-is-null-when-accessed-from-other-file). But TBH - in terms of actual, bare-metal performance, it is probably faster than using `Navigator.of` which is O(N) with N being the widget depth (although N shouldn't be that much). – rmtmckenzie Jul 05 '18 at 10:23
  • My previous comment was intending to say that GlobalKeys are a relatively expensive type of key... and independent of that, using keys should be avoided unless you absolutely need to use them. So my previous statement is misleading, I can't go back and edit it though.... Anyways, Flutter has InheritedWidget or directly passing for getting data from a widget to a sub-widget, and Widget.of() to get back to the parent widget. As a general architecture flutter encourages passing (immutable) data downwards and state upwards. GlobalKey subverts this by allowing state to be passed downwards. – rmtmckenzie Jul 05 '18 at 10:28
  • I agree that in the context of the example - avoiding GlobalKey is possible as splitting the tree into widgets will make possible access Navigator from the context. However there are scenarios where this won't help, e.g. Navigator sits on the sibling branch to the widget which wants to use it. I will update my answer. – robotoaster Jul 05 '18 at 10:34
  • That's fair - there are cases where you need to use a GlobalKey, but if you find yourself using it you should at least consider whether you can refactor to avoid it. Although technically... unless you're doing something special (which can happen, I do), you generally shouldn't have things on sibling branches to your navigator - you should be creating instances of the child widgets in each 'page' (i.e. the `pages` in most of the flutter examples each have a Scaffold) and letting flutter figure out the transitions between them. But that's way beyond the scope of this question & answer =D. – rmtmckenzie Jul 05 '18 at 10:42
  • Very useful to switch to login screen from service class after session expires. – Giddy Naya Mar 20 '22 at 20:43
1

There is an another very different work around about this issue, If you are using Alarm Manager (Android), and open back to your Application. If you haven't turned on the screen before navigation, the navigator will never work. Although this is a rare usage, I think It should be a worth to know.

Tokenyet
  • 4,063
  • 2
  • 27
  • 43
1

Make sure the route table mentioned in the same context:

@override
Widget build(BuildContext context) {
    
    return MaterialApp(
        home: FutureBuilder(
            future: _isUserLoggedIn(),
            builder: (ctx, loginSnapshot) => 
                loginSnapshot.connectionState == ConnectionState.waiting ?
                    SplashScreen() : loginSnapshot.data == true ? AppLandingScreen(): SignUpScreen()
        ),
        routes: {
            AppLandingScreen.routeName: (ctx) => AppLandingScreen(),
        },
    );
}

I faced this issue because I defined the route table in different build method.

svarog
  • 9,477
  • 4
  • 61
  • 77
Avijit Nagare
  • 8,482
  • 7
  • 39
  • 68
1

For me, I was setting the navigatorKey as wrong field on the MaterialApp

final navigatorKey = GlobalKey<NavigatorState>();
class MyApp extends StatelessWidget {

  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // key: navigatorKey, <-- wrong 
      navigatorKey: navigatorKey, // <-- correct
    );
  }
}
Rodrigo João Bertotti
  • 5,179
  • 2
  • 23
  • 34
0

Am a newbie and have spent two days trying to get over the Navigtor objet linking to a black a screen.

The issue causing this was dublicated dummy data. Find Bellow the two dummny data blocks:

**Problematic data **- duplicate assets/image:

_buildFoodItem('assets/plate1.png', 'Salmon bowl', '\$24'),
_buildFoodItem('assets/plate2.png', 'Spring bowl', '\$13'),
_buildFoodItem('assets/plate1.png', 'Salmon bowl', '\$24'),
_buildFoodItem('assets/plate5.png', 'Berry bowl', '\$34'),

**Solution **- after eliminating duplicated image argument:
_buildFoodItem('assets/plate1.png', 'Salmon bowl', '\$24'),
_buildFoodItem('assets/plate2.png', 'Spring bowl', '\$13'),
_buildFoodItem('assets/plate6.png', 'Avocado bowl', '\$34'),

I hope this helps someone,,,,,,,

-1

If the navigator is not working, it can be due to many reasons but the major one is that the navigator not finds the context. So, to solve this issue try to wrap your widget inside Builder because the builder has its own context...

Muhammad Umair Saqib
  • 1,287
  • 1
  • 9
  • 20