Introduction To Navigation And Routing

In this article, we will look at the basics of routing and navigation in Flutter. We will learn how to setup routing in our app so that we can navigate via navigation methods like:

  • push
  • pushNamed
  • pop
  • popUntil

Introduction

Navigating in Flutter is done with the help of the Navigator class that manages a stack of Route objects. The Navigator class provides different methods like Navigator.push(), Navigator.pop(), Navigator.pushNamed() etc. for managing the stack.

Setup

Let’s start by creating a new Flutter project.

DecisionMentor app
flutter create routing

The command above creates a new project called “routing“.

Now let’s start coding by updating the main.dart file so that it looks like this:

//lib/main.dart

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}


class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Routing & Navigation"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Hi, ready to route?',
            ),
          ],
        ),
      ),
    );
  }
}

We have created a basic setup for our app. If you run the app right now, you should see the screen below:

Basic Project Setup For Routing
Basic Project Setup For Routing

Now let’s create a folder called “routes” inside the “lib” folder. This folder will contain the screens that we will route to.

Add a file called “route_A.dart” in this folder with following file content:

//lib/routes/route_A.dart

import 'package:flutter/material.dart';

class RouteA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text("Welcome to Route A"),
        ],
      ),
    ));
  }
}

The Navigator class provides many methods to navigate between the different routes in an application. Also, there can be multiple ways to perform the same navigation.

Navigation Methods
Navigation Methods

For example, we can use either the push method or the pushNamed method to navigate to RouteA from the home page.

The choice depends upon how we have setup the routing in our application.

push Method

push<T extends Object>(BuildContext context, Route<T> route) → Future<T>

pushNamed Method

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

As we can see from the definitions of the these two methods, while pushNamed method takes the BuildContext and a route name as parameters, the push method takes BuildContext with the Route.

Setting up routes for pushNamed method

In order to use the pushNamed method, we should first create a map of widgets that can be navigated to as a String and WidgetBuilder pair.

The MaterialApp has a “routes” property for just this purpose.

We can add routes like this:

//lib/main.dart

return MaterialApp(
      title: 'Flutter Demo',
      routes: {
        'routeA':(context) => RouteA()
      },
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );

To test this route, let’s add a button in the Homepage and navigate with pushNamed method.

//lib/main.dart

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Routing & Navigation"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Hi, ready to route?',
            ),
            RaisedButton(
              child: Text("Let's go through route A"),
              onPressed: () {
                Navigator.pushNamed(context, "routeA");
              },
            ),
          ],
        ),
      ),
    );
  }
}

When you run and press the button, it should navigate to RouteA.

Setting up Navigation for push method

Let’s add another route RouteB which will be identical to RouteA.

//lib/routes/route_B.dart

import 'package:flutter/material.dart';

class RouteB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text("Welcome to Route B"),
        ],
      ),
    ));
  }
}

This time we will navigate to this screen via push method.

As mentioned above, we need to pass an instance of Route class for this. We will make use of MaterialPageRoute which is an implementation of Route that replaces the entire screen with a platform-specific adaptive transition.

So let’s another button for this purpose with the Navigator.push method in the Homepage.

//lib/main.dart

RaisedButton(
              child: Text("Let's go through route B"),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => RouteB()),
                );
              },
            )

Our home page should appear as below and both of our routes should be functioning as expected.

Routing Home Page With Buttons
Routing Home Page With Buttons

Now, let’s try navigating backwards.

Navigating Backwards

Similar to navigating ahead, for navigating back to a certain page is also facilitated by the Navigator methods.

For simply going back one screen, we can use the pop method.

So, we can have something like a back button on our RouteA that can take us back to Homepage as follows:

//lib/routes/route_A.dart

RaisedButton(
              child: Text("Back to home"),
              onPressed: () {
                Navigator.pop(context);
              },
            ),

Another scenario is when we want to navigate back multiple screens. Say the user reached RouteB via RouteA and he wants to get back to the Homepage. In this case, we can simply use the method popUntil so that all intermediate routes are removed from the Stack and the named view is presented.

To see this in action, let’s make few changes in our code.

Let’s first add a new route map as:

//lib/main.dart

routes: {
        'routeA': (context) => RouteA(), 
        '/': (context) => MyHomePage()
      },

The ‘/‘ key route is a special kind of map to the route that should specifically be used to identify the default route only. Also, since we are using the default route map, we can not use the home property as only one of these identifiers can be used.

So make sure to remove this property value as below:

//lib/main.dart

return MaterialApp(
      title: 'Flutter Demo',
      routes: {
        'routeA': (context) => RouteA(), 
        '/': (context) => MyHomePage()
      },
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
    );

This will basically navigate any route named as “/” to MyHomePage.

Next, add a button in RouteA that will navigate to RouteB.

//lib/routes/route_A.dart

RaisedButton(
              child: Text("Let's go through route B from A"),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => RouteB()),
                );
              },
            )

And finally another button in RouteB that will pop back to Home page.

//lib/routes/route_B.dart

RaisedButton(
              child: Text("Go back all the way to home"),
              onPressed: () {
                Navigator.popUntil(context, ModalRoute.withName('/'));
              },
            )

Here we are using RoutePredicate returned from the ModelRoute.withName to pop until the specified route.

You should be able to successfully test these backward navigation now.

Handle Fallback Routes

Sometimes our app might get a request to a route that does not exist. It can happen if somehow a route was deleted but it’s navigation wasn’t updated. In this scenario, Flutter will throw an exception saying the specified route was not found.

To handle such scenarios, we can make use of another property of the MaterialApp called “onUnknownRoute“.

//lib/main.dart

return MaterialApp(
      title: 'Flutter Demo',
      onUnknownRoute: (RouteSettings setting) {
        return new MaterialPageRoute(
                    builder: (context) => NotFoundPage()
        );
      },
....
..

Here, we are asking the framework to redirect all unknown routes to the “NotFoundPage“.

Conclusion

In this post we learned how to setup routing and perform common navigation in Flutter. More advanced topics like passing parameters, and hero animations are covered on the following posts.