01 logo

Navigator 2.0 is not that bad

Use Flutter's Declarative Navigator

By Ankit BansalPublished 3 years ago 9 min read
3
Navigator 2.0 is not that bad
Photo by Sunil Ray on Unsplash

Flutter announced Navigator 2.0 in 2020, after its release many developers gave a lot of bad reviews saying it is too verbose or it is complicated, but actually it is not that bad, that developers think. And we gonna discuss that how it is not that bad and understand the concept behind it.

Before getting into Navigator 2.0, let's first see what was there on Navigator 1.0 and what problems it doesn't solve.

Navigator 1.0

Features:

  1. Provide an API that solves the navigation task in more natural ways like push and pop.
  2. Manages a set of child widgets with the stack discipline.
  3. Follows an Imperative Pattern for handling navigation.

Because it manages the widgets in stack discipline and there wasn't any straightforward way to manipulate the navigation stack, developers started opening issues on Flutter's GitHub page, to provide access to route stack.

Another problem was its imperative pattern, flutter is popular for its declarative way of handling the UI, and using the imperative pattern in one of the basic operations is not a "flutter-y" way of doing something.

Okay, so we understood that it is imperative, so what, if it is working fine and if it is more natural to understand and implement why would anyone learn something else to do the same task. Right? So there must be something that Navigator 1.0 lacks.

So the issues with navigator 1.0 were:

  1. Unpredictable navigation stack.
  2. Do not have any direct way to manipulate the route stack.
  3. No easy way of handling OS events.

To solve the above issues Flutter introduced a declarative way of handling navigation i.e. Navigator 2.0.

Navigator 2.0

Navigator 2.0 solves the major problem in Navigator 1.0 i.e. difficult manipulation of route stack by providing 2 APIs namely, Pages API and Router API.

Now, before getting deep into the two APIs and their concepts let's see an example and try to understand the problem that we might face with navigator 1.0 and how easily Navigator 2.0 solves that.

Suppose... we are building an app and we have a requirement of adding a screen in between the navigation stack during the runtime, i.e., initially we have Screen A and Screen B in the stack and we want to add a new screen Screen C in between the two screens during runtime, So that our stack changes from A->B to A->C->B, you will understand it better from the following figure.

Change Navigation Stack in Runtime

Now if we want to do the above stuff using Navigator 1.0, How would we do that?

So you must have thought of some tricky workaround of using navigator.replace() or navigator.pushReplacement() or something else and it is true that we have to deal with this situation using some hacky workaround, one such way is like this :

Using Navigator 1.0

Here we use Navigator.pushAndRemoveUntil() method to remove the screens from the stack till we reach the last screen (i.e Screen A) and then we push ScreenC after this, we use the simple Navigator.push() method to push another Screen (i.e. Screen B).

Now we will solve the above problem using Navigator 2.0

Using Navigator 2.0

Yes you saw it right, we just do a simple insert operation at the second last position of the pages list and that's it, the navigation stack is changed. Of course, we have to call notifyListeners or setState to notify all the listeners about the changes in the page list.

Details of Navigator 2.0

So far you have seen a glimpse of the capabilities of Navigator 2.0, let's dive deep into the details and implementation of Navigator 2.0.

The new navigator comes with 2 new APIs:

  1. Pages API
  2. Router API

1. Pages API: The first thing in the pages API is that there is a new class called page class. The page will create the route that will later be placed on the route stack.

You(The developer) will provide a list of page objects to the new navigator widget in its constructor and the order of pages in this list is the same as the order of routes corresponding to the pages in the history stack. So, when the list of pages given to the navigator is updated, the new list is compared with the old list, and the route history is updated accordingly.

Remember Page objects are immutable and route objects are mutable.

Our new navigator constructor also takes a new onPopPage callback. The navigator calls this method usually in response to a Navigator.pop() call, in order to ask that, the given route corresponding to a page should be popped out or not. If the receiver of this call agrees to pop, then it calls the didPop() method on the route and if that was also successful, the list of pages (that we have added to the navigator) is updated and that list no longer includes the page corresponding to the popped Route and the onPopPage callback returns true. If the receiver of the onPopPage callback doesn't want the Route to be popped it must simply return false (without calling didPop on the Route).

The onPopPage callback is called only for the topmost Page in the stack.

Backward Compatibility With Imperative Navigation

What if my project already contains imperative navigation then how will I use it with the new navigator?

We know that the existing imperative API of the Navigator adds Routes to the history stack (via push() and friends), that doesn't correspond to a Page. So, to minimize the breakage, the flutter team introduced The Pageless route. So when a user does the navigator.push(), pageless routes are tied to the Route below them in the history stack that does correspond to a Page.

There is also an optional field in the navigator constructor and that is TransitionDelegate. It was introduced in order to solve these 2 questions:

Que1. Should the Route animate in/out when it is added/removed or should it just appear/disappear?

Que2. When Routes are added and removed at the same location in the history stack how should they be ordered for the duration of the transition?

we are not going to discuss the TransitionDelegate in this article.

2. Router API: Router API contains a Router Class. It listens for routing information from Operating System and configures the list of pages to be displayed by the navigator. Operating system events include device back button intent, set initial route, push route, and Url update(in case of the web).

So the router API manages what's currently shown on the screen. Instead of using the imperative API to show a new Route in response to the user tapping a button, the button's click handler will modify a particular state. The Router is registered to listen to changes in the app state and will rebuild with a newly configured Navigator to reflect those changes.

Change Navigation on the basis of App State

Normally with widgets, when you update any state using setState, the widget rebuilds itself and update the UI on the basis of the state, similarly, in the above diagram, you can see that if the user taps a button and that button changes the state then the router will get notified and a new route will be built according to the changes. This is the declarative way of navigation.

To wire up all those operating system events, router API uses a few delegates like:

  1. RouteInformationProvider - Transfer information from the OS to router and vice-versa.
  2. RouteInformationParser - Takes the information from RouteInformationProvider and parses it into a list of RouteSettings where each element of the list represent a page that should be pushed onto the navigator and these routeSetting configuration will be used by router delegate to perform the navigation.
  3. RouterDelegate - ❤️ of the Router API. It handles all incoming operating system events. We will look into it a bit deeper.
  4. BackButtonDispatcher - Informs the RouterDelegate that the system's back button has been tapped and so the user would like to go to the previous route.

RouterDelegate: The flutter framework doesn't provide a default implementation of RouterDelegate, so you need to define this. It acts as a builder for the router widget and builds a navigator widget. So the class which extends RouteDelegate gets the power to decide which page to show.

The routerDelegate is also informed about routing-related operating system events like:

  • popRoute is called when the backButtonDispatcher notifies the Router that the user has pressed the system's back button.
  • setInitialRoutePath is called shortly after the Router has been built for the first time with the initial route information obtained from the routeNameProvider. The route name is parsed by the routeNameParser before it is passed into this method. By default, this method is just forwarded to setNewRoutePath.
  • setNewRoutePath is called whenever the routeNameProvider signals that a new route should be shown. The route name is parsed by the routeNameParser before it is passed into this method.
Router Delegate Implementation

Here in the above code, we have implemented the build method (line 9) which returns a Navigator, and as we already know that navigator takes a list of pages, onPopPage callback, and a unique key to separate it from others.

The currentConfiguration (line 29) property is called by the Router when it detects that a route information may have changed as a result of the rebuild.

The setNewRoutePath (line 32) is called by the Router when the Router.routeInformationProvider reports that a new route has been pushed to the application by the operating system.

So to sum it up,

  1. the router delegate will return navigator in build function,
  2. the navigator widget takes a list of pages and onPopPage callback,
  3. the list of pages contains the MaterialPageRoute which tells which screen to be displayed and onPopPage callback will handle the pop behavior of the app with the current navigator,
  4. the setNewRoutePath will be called every time the OS events take place with respect to the navigator and updates the pages list accordingly.

That's it, you know everything you need to know about navigator 2.0 before using it. But still, you want a code example and I know that, so for that, I found a great example. Click on the link, clone the project and try to understand.

Reference: Flutter Navigator 2.0 and Router by Flutter Team

You must be feeling, that's a lot of things to understand just for a basic operation like navigation and that's why it gets so many negative comments from developers across the world, I feel the same initially but once you understood and tried it on your project you will understand that it is not that bad and you can do a lot of tricky navigation very easily

If you stuck somewhere, and think you need a helping hand, feel free to mail me @ [email protected], we both will try to solve your problem.

You can also connect with me on LinkedIn or on Twitter.

list
3

About the Creator

Ankit Bansal

Software Engineer.

Learner.

Reader.

Know more about me @https://ankit986.github.io/portfolio/

Reader insights

Be the first to share your insights about this piece.

How does it work?

Add your insights

Comments

There are no comments for this story

Be the first to respond and start the conversation.

Sign in to comment

    Find us on social media

    Miscellaneous links

    • Explore
    • Contact
    • Privacy Policy
    • Terms of Use
    • Support

    © 2024 Creatd, Inc. All Rights Reserved.