10

I cannot understand the reason why someone should use named routes, with Navigator.pushNamed(), instead of the normal way with Navigator.push().

The tutorial page states that:

if we need to navigate to the same screen in many parts of our apps, this can result in code duplication. In these cases, it can be handy to define a “named route,” and use the named route for Navigation

Duplication

How will the duplication be generated when using simple routing and how it will can be eliminated with the use of named routes?

I fail to understand what is the difference of

Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => SecondRoute()),
  );

from

Navigator.pushNamed(context, '/second');

in the context of duplication.

Peter Haddad
  • 78,874
  • 25
  • 140
  • 134
Themelis
  • 4,048
  • 2
  • 21
  • 45
  • I would love to hear some actual arguments for named routes. I guess that on the web, they are important because you actually need to support manually typed URLs. In Flutter (on mobile), they give you only more dynamism (no type checking for route parameters any more) and centralisation of route declaration (I fail to see how that's a benefit!). Still, I might be missing something important. – cubuspl42 Apr 28 '20 at 12:33
  • 1
    Maybe it's about this `MaterialPageRoute(builder: (context) => ...` part duplication? – cubuspl42 Apr 28 '20 at 12:47

7 Answers7

20

Consider you go with Navigator.push() in many widgets:

// inside widget A:
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SecondRoute()),
);

// inside widget B:
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SecondRoute()),
);

// inside widget C:
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SecondRoute()),
);

Now let say you need to change your App and the widget SecondRoute needs to receive a value on it's constructor. Now you have a problem, since you have multiple copies of the same code on several locations, you need to make sure you will update all of those copies, which can be tedious and error prone:

// inside widget A:
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SecondRoute(
      title: 'Title A',
  )),
);

// inside widget B:
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SecondRoute(
      title: 'Title B',
  )),
)),
);

// inside widget C:
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SecondRoute(
      title: 'Title A',     // ERROR! Forgot to change the variable after a copy/paste
  )),
)),
);

Now let's consider you go with named routes.

Firstly I would never recommend anyone to actually use the name directly for navigation, but instead use a static variable reference, this way if you need to change it in the future its way simpler and secure, as you can't forget to update it anywhere, like this:

class Routes {
  static const String second = '/second';
}

Another way is to have a reference inside the route itself, a static const String inside SecondRoute, so we can use it as SecondRoute.routeName. It's a matter of personal preference IMO.

Then your widgets will navigate using:

// inside widget A:
Navigator.pushNamed(context, Routes.second); // Routes.second is the same as '/second'

// inside widget B:
Navigator.pushNamed(context, Routes.second);

// inside widget C:
Navigator.pushNamed(context, Routes.second);

Now if you need to pass a parameter to SecondRoute upon creation you can do it in a centralized location using the MaterialApp onGenerateRoute, as this tutorial explains in more detail. Your code will be changed to:

// inside widget A:
Navigator.pushNamed(context, Routes.second, arguments: 'Title A');

// inside widget B:
Navigator.pushNamed(context, Routes.second, arguments: 'Title B');

// inside widget C:
// You can still make mistakes here, but the chances are smaller.
Navigator.pushNamed(context, Routes.second, arguments: 'Title C');
MaterialApp(
  onGenerateRoute: (settings) {
    if (settings.name == Routes.second) {
      final String title = settings.arguments;

      return MaterialPageRoute(
        builder: (context) => SecondRoute(title: title),
      );
    }
  },
);

The amount of duplicated code is decreased, but on the other hand the onGenerateRoute code gets more complex as you make more routes, as all of their creation will be centralized there, so IMHO it's more about a personal preference then a general guideline.

Peter Haddad
  • 78,874
  • 25
  • 140
  • 134
Michel Feinstein
  • 13,416
  • 16
  • 91
  • 173
  • Thank you for your answer. It's detailed and the code snippets help to understand your point. While I understand what you're saying, I absolutely don't understand the conclusion. For now, I'll just ask a single question. In one of your snippets, you added a comment: `// You can still make mistakes here, but the chances are smaller.`. Question: **Why?** – cubuspl42 May 04 '20 at 07:05
  • You can make mistakes because it's common to **copy-paste** repetitive code and forget to change it. On that particular case the chances are smaller because the code is simpler so you can easily spot any errors, whereas in the more complex codes the small parts to be changed can get hidden in all the extra boiler plate. – Michel Feinstein May 04 '20 at 23:35
  • 3
    So copy-pasting the 67-character long snippet based on dynamic typing (route name is `String` and `arguments` is `Object`!) with a potential for desynchronisation (you can by accident give arguments meant for `B` to route `A`) snippet is less dangerous than copy-pasting 97-character long snippet which is fully statically typed (invocation of a constructor) and no potential for desynchronisation (arguments are tied to constructor name by static typing)? – cubuspl42 May 05 '20 at 07:28
  • 1
    Also, the longer one has potential for shortening to 69 with standard tools like extension methods: `Navigator.of(context).pushM((ctx) => SecondRoute(title: 'Title A'));`. – cubuspl42 May 05 '20 at 07:29
  • As I said, I think this is opinion based and each person will have a feeling on this. If you think the article you read from Google needs more clarification, there's a button on top that allows you to open a bug report and there you can ask for further clarification from them. – Michel Feinstein May 05 '20 at 22:44
  • @MichelFeinstein great explanation! I have a quick question, I have used `Navigator.push()` in few places and now I want to implemented namedroutes, would it be ok to only define new routes by using the `Navigator.pushNamed()' .Will this cause any conflicts? – Manas Sep 13 '21 at 10:12
4

Push and PushNamed have the similar effect, Push will switch to the route you specified while PushNamed will switch to the route with the route name specified.

What the Tutorial page means for duplication is duplication of code not duplication of routes.

For instance, you have a route where you would want to check whether the user is signed in and show the corresponding page

Using Push only: Page1:

//This is page 1....
 RaisedButton(
          child: Text('Go to second'),
          onPressed: () {
            if (user.state = "login") {
              Navigator.of(context).push(
               MaterialPageRoute(
                builder: (context) => SecondPage(),
              ),
             )
            }else{
              Navigator.of(context).push(
               MaterialPageRoute(
                builder: (context) => SecondPageAnonymous(),
               ),
              )
            }
          }
        )
....

In another page, Page2, you will need to repeat the same code:

//This is page 2....
 RaisedButton(
          child: Text('Go to second'),
          onPressed: () {
            if (user.state = "login") {
              Navigator.of(context).push(
               MaterialPageRoute(
                builder: (context) => SecondPage(),
              ),
             )
            }else{
              Navigator.of(context).push(
               MaterialPageRoute(
                builder: (context) => SecondPageAnonymous(),
               ),
              )
            }
          }
        )
....

With PushNamed, you just have to declare it once and you can basically reuse it over and over again.

In your onGenerateRoute:

onGenerateRoute: (settings) {
switch (settings.name) {
  case '/':
    return MaterialPageRoute(builder: (_) => FirstPage());
  case '/second':
    if (user.state = "login") {
      return MaterialPageRoute(
        builder: (_) => SecondPage()
      );
    }else{
      return MaterialPageRoute(
         builder: (_) => SecondPageAnonymous()
     );
    }

  default:
    return _errorRoute();
 }
},

Now in ANY pages in your project, you could do this:

 Navigator.of(context).pushNamed('/second')

Without needing to repeat the checking of sign in or even the error handling every time you used it. The obvious benefit is that you can stay consistent throughout the app by preventing duplicate code piece, instead of repeating it again and again.

Now, this however DOES NOT prevent duplicates of routes! There is no different between push and pushNamed in this context!

But since your routes are now named, you can do popUntil('/') easily to go back to the first instance of the route, instead of creating it again or PushReplacementNamed.

0xCCY
  • 555
  • 2
  • 10
  • That's a good observation. Well, *maybe* the tutorial authors meant that, because they didn't design their example like this. I'm a fan of using the simplest possible tool for a given problem. For adding two numbers I use +, etc. Let's start with unnamed routes: `Navigator.of(context).push(MaterialPageRoute(builder: (context) => SecondPage())))`. **Problem**: Well, actually this should be `SecondPage` for logged-in users and `SecondPageAnonymous` for other... I'd call this abstraction `second`. **Solution**: `Navigator.of(context).push(MaterialPageRoute(builder: (context) => second())))` – cubuspl42 May 05 '20 at 07:07
  • Of course, here `second` is a helper function that does the same switching as your snippet. I assume that `user` object is given, maybe global. By the way, I don't know if your whole routing strategy works if the application state isn't stored in globals. – cubuspl42 May 05 '20 at 07:10
  • If I'd want to remove the `MaterialPageRoute(builder: (context) =>` duplication, I'd declare an extension method on `Navigator.pushM(RouteBuilder builder)`, so it's used like `Navigator.of(context).push((ctx) => MyRoute1())`, etc. Am I mad, or is the world mad? – cubuspl42 May 05 '20 at 07:14
  • For global you might want to look into Providers. Here I am just giving an example, assuming that you have the user in the global space. – 0xCCY May 05 '20 at 07:20
  • With Providers you'd need to push the `if` to the builder, right? Is `context` available in `onGenerateRoute`? – cubuspl42 May 05 '20 at 07:25
  • There's a few way to deal with duplicates. Push is just going to add a new page on the Stack. To remove a screen you need Pop. For instance, if you want to remove duplicates, one way you can do is to PopUntil the root page (or the previous page that you want to remove duplicates of). But if you just want to replace the existing page, you can use pushReplacement. – 0xCCY May 05 '20 at 07:30
  • No, you will need to pass the value from the provider as argument into the pushNamed like so, then you can access it in the `onGenerateRoute` with `Settings.arguments`: Navigator.of(context).pushNamed( '/second', arguments: provider.UserState, ); – 0xCCY May 05 '20 at 07:40
2

The only advantage I can see using Navigate with named routes is to have routes declared inside your MaterialApp, so that developer can only be used assigned routes i.e widgets, pages,

If anyone uses other than that, It will give an error 'onUnknownRoute is called.'

Jitesh Mohite
  • 31,138
  • 12
  • 157
  • 147
  • So the first benefit is limiting the developer (although not really, because they can always... just add new routes, to the MaterialApp or however; it's their app). And the second benefit is the _runtime_ error, instead of a compile-time error with unnamed routes. – cubuspl42 May 05 '20 at 06:56
  • 4
    How is runtime error better than compile time error? – Ben Butterworth May 05 '20 at 08:24
2

Here is my beginner flutter thoughts:

It makes the code cleaner: Without declaring the routes at the level higher widgets, new screens will appear out of nowhere, in response to anything that happens in the app. It is much easier to understand the navigation skeleton/ structure when you declare the routes together, and even more so at a higher widget, especially for other developers. Of course, this doesn't help with understanding exactly when those routes are actually navigated to, but its a small improvement, and brings us back into the declarative paradigm. The hint provided by the declared routes will help a newer developer understand your navigation flow.

Ben Butterworth
  • 22,056
  • 10
  • 114
  • 167
  • So you can easily learn the set of routes used in the project with named routes. That's cool, but you can also do that in many other ways, without all the dynamism and framework structure, for example by having a `routes` directory in your project and putting your route files there. Then you can also use IDE `Find Usages` functionality on your route symbol. It's not even a draw here, it's 1:0 for unnamed routes. – cubuspl42 May 05 '20 at 07:59
  • By the way "unnamed" sounds like a degrading term. It's like... these routes didn't have a name, and we all know that _name_ is something that gives identity. They _do_ have a name. In the example given by the OP, route's name is `SecondRoute`. It's a great name. – cubuspl42 May 05 '20 at 08:02
  • If you have a routes file, it would only be imported/ used when `Navigator.push` is called. So when you're looking at the overall structure of the app, starting from `main` and going down, you won't see any information about navigation, and all the navigations are just side effects of pressing buttons, where these routes have never been declared before. If I was looking at a project, for this reason, I would prefer that the navigation was declared at the widget creating the widgets that are changing (routes). – Ben Butterworth May 05 '20 at 08:21
  • It's a nearly philosophical reasoning. When you're starting from `main()` and going down you're also seeing `main.dart` and `routes` directory right next to it. You might add a comment over your `main()` like `// See `routes` directory to find routes`, or something. From my perspective, these are soft problems that can be solved with a directory and a comment versus over-frameworking and loosing the static typing for no technical reason. – cubuspl42 May 05 '20 at 08:34
  • But if we're being philosophical, I don't understand why navigation as a side effect is something wrong. Navigation, from the flow perspective, is a side-effect of user interactions. A set of all possible routes can be reasonably grouped only statically, which is perfectly solved by grouping route files in a folder. – cubuspl42 May 05 '20 at 08:36
  • I found this question about side effects, although it is about functional programming, one of the answers mentioned its impossible to prevent side effects. But, 'The key is to limit side effects, clearly identify them, and avoid scattering them throughout the code'. I don't see why I have to use this quote as to me, it is much cleaner when declaring the routes, as it clearly identifies routes, and reduces scattering of routes in other files. Its also not a philosophical argument, but a pragmatic one. When you go up and down the widget tree to understand the program, you *will* find routes. – Ben Butterworth May 06 '20 at 08:34
  • It is philosophical, because you're using reasoning from functional programming in a context which is not functional programming at all, in any way. In this context, there's no change in side-effects, no matter which routing solution you use. _Unless_ you bring the concept of "side effect" far beyond its actual meaning in programming lingo, and apply it to the navigation in GUI apps. Then you say that route change is not a side effect, only because named route system "forced" you to see it's declaration in the `main` file. And it _is_ a side effect if it didn't. – cubuspl42 May 06 '20 at 08:41
  • The bare fact that you saw a declaration of something has nothing to do with side effects. I can use a pure function declared in another file, I can use a side-effectful procedure declared a few lines aboce, these are totally different topic. – cubuspl42 May 06 '20 at 08:44
  • Using side effects for GUI is not 'far beyong its actual meaning'. Don't take what you believe as dogma. Maybe you don't what a side effect is. [This will help](https://softwareengineering.stackexchange.com/questions/40297/what-is-a-side-effect) – Ben Butterworth May 06 '20 at 08:45
  • You linked to the most basic non-controversial definition of a side effect. With this definition, the route change after calling `push` is always a side effect, no matter how that route is declared. – cubuspl42 May 06 '20 at 08:47
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/213220/discussion-between-ben-b-and-cubuspl42). – Ben Butterworth May 06 '20 at 08:48
  • I'm sorry if I offended you. If you'd like to point out what was rude in my words, I'll take care in the future. – cubuspl42 May 06 '20 at 08:48
1

For folks visiting this question in 2022. Flutter actually now recommends not using named routes

Note: Named routes are no longer recommended for most applications. For more information, see Limitations in the navigation overview page.

https://docs.flutter.dev/cookbook/navigation/named-routes

qHack
  • 182
  • 3
  • 12
0

If you use push(), you have to import the file in which SecondRoute is located every time you need to navigate to that screen. This is excessive code duplication for big projects that you need to navigate around the different screens.

If you use pushNamed(), you need to define the routes only once in your MaterialApp. Then, you can navigate to any screen from anywhere without repeating the same thing like you have to with push().

Another big reason to choose PushNamed() over the other one is to be able to build your own navigation system with it. You can decide whether or not routes are available for certain users even before they navigate to the screen.

Stewie Griffin
  • 4,690
  • 23
  • 42
  • By saying "without repeating the same thing", do you mean `MaterialPageRoute(builder: (context) =>`, i.e. construction of the `Route` object? – cubuspl42 May 04 '20 at 06:58
  • I didn't really get the "decide whether or not routes are available for certain users" part, could you elaborate? – cubuspl42 May 04 '20 at 06:59
  • @cubuspl42, You don't need to repeat not only `MaterialPageRoute`, but also `import` to import the file that includes the widget. I created a navigation class for my projects, so when I want to navigate from one screen to another, I check the user state. If the user is not logged in, it navigates to the login screen. So, I don't need to check it in each screen. In addition to that, I keep all page route transition types in the same class, so I can manage them easily. However, with `Navigator.push()`, I wouldn't be able to do that. – Stewie Griffin May 04 '20 at 09:30
  • I don't believe that tutorial authors, or anybody to be honest, actually consider imports to be code duplication. In these days they are automatically managed and provide much more benefits than cost. In the old days, `#include`s had less "code duplication" because one "line" introduced a massive amount of symbols to the global scope. Nobody was happy with that. – cubuspl42 May 05 '20 at 07:33
  • The navigation and "conditional routing" argument sounds valid, it's one of main points in [another answer](https://stackoverflow.com/a/61607084/1483676). You can check the comments. – cubuspl42 May 05 '20 at 07:34
0

for understanding why we should use Navigator.pushNamed instead Navigator.push first let's be familiar with Navigator methods. did you ever heart about Navigator.popUntil or Navigator.pushAndRemoveUntil? we use Navigator.popUntil when we want to pop in the stack to a specific route. if you check the documentation you can find that it's very easy to use these methods with the pushNamed method. also, check all methods in the documentation. when I try to understand routing in flutter this article was very useful for me. and as a disadvantage, it's very hard to handle parameters in this approach. you should create onGenerateRoute and handle parameters for each route.

Payam Zahedi
  • 827
  • 7
  • 20