Navigating Flutter Part 2

A look into Flutter’s Navigation System

In part 2 of this series, we’ll continue our look at Flutter’s navigation system and examine another approach used to navigate from screen to screen in your Flutter app. This approach works with ‘named routes’, using the static function, Navigator.pushNamed(). We’ll also look at what’s involved when you go back through the ‘route stack’ with the static function,Navigator.pop().

Image for post
Image for post
Flutter Navigation Part 1

I Like Screenshots. Click For Gists.

As always, I prefer using screenshots in my articles over gists to show concepts rather than just show code. I find them easier to work with frankly. However, you can click or tap on these screenshots to see the code in a gist or in Github. Further, it’s better to read this article about mobile development on your computer than on your phone. Besides, we program mostly on our computers — not on our phones. Not yet anyway.

Let’s begin.

Image for post
Image for post
Other Articles by Greg Perry

The MaterialApp widget has a named parameter called, routes. It takes in a Map object of type,<String, WidgetBuilder>{}, with a function-type alias, WidgetBuilder.This alias defines the following function signature to create a widget, Widget Function(BuildContext context);. Below is a screenshot of the example app we’re using in this article called, navigate_with_named_routes.dart. You can see the ‘route table’ contains two entries, and their map keys are strings — the name of the route, and each map value is a function that returns a widget. Pretty straight forward.

Image for post
Image for post
navigate_with_named_routes.dart

What’s In A Name?

The parameter, routes, is referred to as the ‘routes table’, and it works with the ‘named routes’ approach (Navigator.pushNamed()) to have you move from screen to screen. It’s suggested such a table is implemented when apps have either a large number of routes (screens) or when the app has many separate screens repeatedly navigating to the same screen — all to reduce code duplication. It’s suggested, in such situations, a ‘routes table’ would also be easier to maintain.

An example of the ‘named route’ approach is displayed in the screenshot below. The static function, Navigator.pushNamed(), is called passing in two positional parameters: BuildContext context and String routeName.

Image for post
Image for post
navigate_with_named_routes.dart

Note the pushNamed() function’s signature below. In the example above, the command neglects to specify the return value’s data type. It’s not mandatory, but I would say is a better practice is to include the return value’s data type between brackets: Navigator.pushNamed<dynamic>(context, '/second/);

static Future<T> pushNamed<T extends Object>(
BuildContext context,
String routeName, {
Object arguments,
})

The function returns a Future object of that specified data type. Dealing with a Future returned value implies you’re free ‘to wait’ at that command line for a returned value using the operator, await. The example above does just that.

Note, there’s a third named-parameter in the pushNamed() function many are not aware of called, arguments. We’ll look into that shortly. Right now, let’s walk through the code and see what happens when the pushNamed() function is called. This will involve a running commentary on a series of screenshots below, and we’ll start with the pushNamed() function itself.

And so inside the function, we see the commonly used static function, Navigatoar.of(). It retrieves the State object, NavigatorState, to then call, in turn, its own pushNamed() function passing on the ‘route name’ and any arguments. See below.

Image for post
Image for post
navigator.dart

Now, in its pushNamed() function, we see the conventional push() function is then used, but not before the private function, _routeNamed, takes in the the ‘route name’ and any arguments and returns a Route object.

Image for post
Image for post
navigator.dart

Looking into the private function, _routeNamed, you’ll find the onGenerateRoute() function defined in the StatefulWidget, Navigator, will then take you to the widget, WidgetsApp, and its own private function, _onGenerateRoute(). Still with me?

Image for post
Image for post
navigator.dart

It’s in that private function (see below), where the ‘routes table’ is first accessed looking up the specified route name with, widget.routes[name];. Looking carefully, you can see the assigned variable, pageContentBuilder, is tested for null. If the provided name is indeed found in the Map object, routes, a Route object is returned using the corresponding WidgetBuilder found in the Map object.

Image for post
Image for post
app.dart

Note, you may remember from Part 1, the routine, widget.pageRouteBuilder<dynamic>(), is the anonymous function supplied to the WidgetsApp as you see in the screenshot below. It takes in from the function, _onGenerateRoute(), the ‘RouteSettings’ and ‘WidgetBuilder’ objects and returns the appropriate ‘Route’ object depending on the ‘Interface design:’ Material or Cupertino.

Image for post
Image for post
app.dart

A Route With No Name

Returning to the _WidgetsAppState object and its function, _onGenerateRoute, listed again below, if the route name is not found in the Map object, routes, and the variable, pageContentBuilder, is then null, an alternative operation may occur. Note the red arrow below highlights the MaterialApp’s onGenerateRoute parameter.

Image for post
Image for post
app.dart

In the screenshot below, that parameter, onGenerateRoute, is not null in this particular example app. It’s supplied a static function from the class, Router. And with that, note the ‘route table’ has merely two entries one of which is assigned as the ‘home screen’ (initialRoute: '/') and so is already called and displayed when the app first starts up. So, what happens when the button, ‘Third screen’, is pressed in the example app?

What happens is the variable, pageContentBuilder, is null, but the instance field, onGenerateRoute, is not. Thus, the defined routine in the class, Router, (below on the right) is called passing in the object, settings, of type RouteSettings. And so when the button, Third screen, is pressed, the static function, Navigator.pushNamed(), is called (below on the left) with the route name, ‘/third.’ As you see on the right, however, the ‘Router’ routine has an entry in its switch-case that matches that name. With that, the appropriate MaterialPageRoute object is returned all the way back to the NavigatorState’s private function, _routeNamed(). Clear as mud.

With all that, a Route object is now supplied to the push() function while continuing to specify the returned value data type, T. Note, it’s required that generic type be extended by Object.

Image for post
Image for post
navigator.dart

We’ll now look into the push() function and see what’s involved. Note, below you see the Route object is added to the List instance field, _history. As you may know, it is this variable that represents the ‘stack of routes’ that comes about when going from screen to screen. The function ends with the getter, popped. Of course, since this function returns a Future which is to contain the result value of a particular data type, T, this is where we essentially wait for the user to return from the screen just selected.

Image for post
Image for post
navigator.dart

Pop Completes A Future

As it states in the documentation below, the getter, popped, waits here until the ‘pushed’ route is completed and is ‘popped off.’ Keep in mind, the generic type, T, is passed on so to identify the data type that is to be returned as a result if any.

Image for post
Image for post
navigator.dart

What’s Your Argument?

Let’s take a quick look back at the pushNamed() function with that third named-parameter you may not be aware of called, arguments. As you know now, that parameter is of type, Object, allowing you to pass *any* object you like and pass it along to later be cast to the specific class type you’re expecting. Let’s walk through the code now to see what happens when you pass ‘an argument’ in the pushNamed() function. In the example app, a pretty self-explanatory String object is passed as an argument.

Image for post
Image for post
navigate_with_named_routes.dart

And so, when one of those pushNamed() functions is called and we’re to navigate to the third screen, for example, the RouteSettings object contains any and all arguments that are to be passed along. This is true for the fourth screen as well. Got it?

Image for post
Image for post
navigate_with_named_routes.dart

Note, the RouteSettings object provides the arguments field instance, granted, but it is up to the developer to then determine how to deliver that object to the subsequent screen if need be. The most obvious approach I would say is to include the object as an explicit parameter to the StatelessWidgt in this case. And that’s about it when it comes to that unknown ‘arguments’ parameter.

Image for post
Image for post
navigate_with_named_routes.dart

When It Pops

Finally, let’s walk through the code involved when the ‘route stack’ does retreat back to the previous screen with the command, Navigator.pop();. Looking in the pop() function, we see the Navigator's State object, NavigatorState, is retrieved and its own pop() function is called in turn — passing in the optional result value of data type, T.

Image for post
Image for post
navigator.dart

A Route To History

It’s in the State object, NavigatorState, where we again see the ‘route stack’ in the form of a List object called, _history. The last entry in that List object is retrieved containing the route that’s just been ‘popped.’ This ‘route entry’ object has its own pop() function and it too is called passing in the result value if any. As you see in the screenshots below, there are three more functions called until finally the Completer object waiting in the push() function is indeed completed with the specified result value.

Image for post
Image for post
navigator.dart
Image for post
Image for post
navigator.dart
Image for post
Image for post
navigator.dart
Image for post
Image for post
navigator.dart

In the example app, when clicking back from the Third screen, we see in the screenshot below that the result value returned is the string, ‘Third Screen argument.’

We’ll continue our examination of the Navigation system offered by Flutter with subsequent articles. There are still other options available to you when moving around your app. Flutter has a lot to offer. You just have to walk through the code to find it.

Cheers.

Image for post
Flutter Navigation Part 3

→ Other Stories by Greg Perry

Image for post
Image for post
DECODE Flutter on YouTube

Freelance Developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store