Learn More

Try out the world’s first micro-repo!

Learn More

Flutter Navigation Overview and Best Practices

Flutter Navigation Overview and Best Practices

Navigation is one of the most fundamental components of any app and is critical to ensuring that your app is providing a good user experience. With Flutter, there are two methods to implement navigation: the `Navigator` widget and the `Router` widget. Both these approaches have advantages and disadvantages, and one might be better suited than the other for your application.

This article will first explain why Flutter navigation is important, and then show you how to implement navigation in your Flutter apps using the Navigator widget and the Router widget.

What’s Flutter Navigation?

Let’s say that your app looks like this:

The base state of our app

This home page has a button that leads to a login page. Now, you need to code this button so that it displays the login page upon clicking. This can be accomplished using Flutter navigation.

Through the navigation stack, which is explained fully later in the article, Flutter navigation provides the ability to show different pages to users depending on their actions, for example, clicking a button leads to the display of a different page.

Why Getting to Grips with Flutter Navigation Is Important

Understanding Flutter navigation is important and critical for a good user experience for a few reasons:

  • An app consists of multiple pages presenting different pieces of information. If users can’t navigate to and from those pages, they won’t be able to access all the content, rendering the app useless.
  • When users struggle to navigate through your app, the result is bad user experience. If you don’t understand Flutter navigation properly, you won’t be able to implement a navigation system that works effectively for your app, which ties back to optimizing the user experience.
  • A strong understanding of the navigation system will help you deal with technical issues that come up in developing the app. Copy-pasting code won’t help during troubleshooting.

How to Implement Flutter Navigation

Continuing with the example described above, this section will demonstrate how you can start implementing Flutter navigation.

The home page of the app that’s used an example in this article starts off with the following code:

import 'package:flutter/material.dart';
void main() {
  runApp(const MyApp());
}
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}
class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Homepage'),
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Center(child: Text('Click below to go to login page', style: TextStyle(fontSize: 23),)),
          ),
          ElevatedButton(onPressed: () {}, child: Text('Login'))
        ],
      ),
    );
  }
}

In this example, you have one main widget called `MyApp` and another widget called `MyHomePage`, containing your app bar, text, and button. `MyApp` references `MyHomePage` for the content of the page.

With the basic code for the home page in place, you now need to work on your button so that it displays another screen upon clicking. For that, let’s explore the two navigation methods.

The Navigator Widget

As the name suggests, the `Navigator` widget is the most popular option in Flutter for navigation. The two main functions you need to know for `Navigator` are `Navigator.push()` and `Navigator.pop()`.

However, before you dive into using these functions, you need to understand a fundamental concept, the navigation stack.

A diagram explaining the concept of the navigation stack

You can think of the navigation stack as a pile containing the pages in your app. The page at the top of the bundle will be displayed to the user, while the others remain out of sight.

When you `push` a page, it goes to the top of the navigation stack, and that’s what the user sees. Using `pop`, you remove the page at the top of the navigation stack, displaying the page underneath it to the user. So you can either add a page on the stack for navigation between two pages or remove one from the stack.

By understanding the role of the navigation stack, you’ll be able to effectively use the functions like `push` and `pop` as a subset of the `Navigator` widget. Let’s continue with the use case and implementation now.

The Navigator Widget Use Case

You should use the `Navigator` widget when:

  • You don’t need to preserve the state of the underlying pages on the navigation stack;
  • Nor do you need to store the browsing history of the user; and
  • You just want to pass simple data to other pages.

This widget provides a straightforward and clean way to navigate between pages on a mobile app where the user cannot directly access the page URLs. You’ll be able to add navigation without writing too much code or logic.

Navigator.push()

You can add pages to the call stack with the `Navigator.push()` function. For example, if you want to go from the home page to the login page, you should use the `push()` function as follows:

ElevatedButton(
  onPressed: () {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => LoginPage(),
      )
    );
  },
  child: Text('Login')
)

For the navigation to work, you only need to edit the `ElevatedButton()`, because that’s how the user will hop from one page to another.

Annotated screenshot of the button and its code

Note: For reference, here’s an unannotated version of the same screenshot.

Unannotated screenshot of the button and its code

In the `onPressed()` property of the button, you added a `Navigator.push()` function where we have passed the `context` and the `MaterialPageRoute` widget.

The `MaterialPageRoute` helps create a route object that can be pushed onto the navigation stack. You define `LoginPage()` as the destination, which is a widget in your `login.dart` file for the login page.

As you can see, this process is relatively straightforward. Now, let’s look at how you navigate back to other pages on the navigation stack.

Navigator.pop()

This simple function is used to go back to the previous route on the stack. On your login page, you add a simple button using the following code:

import 'package:flutter/material.dart';
class LoginPage extends StatelessWidget {
  const LoginPage({ Key? key }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Login Page"),
      ),
      body: Column(children: [
        Text('Hello World!'),
        ElevatedButton(onPressed: () {}, child: Text('Go Back'))
      ],),
    );
  }
}

You can add the functionality to go back to the home page upon clicking the button with the following modifications to the button code:

ElevatedButton(
  onPressed: () {
    Navigator.pop(context);
  },
  child: Text('Go Back')
)

A short line inside the `onPressed()` function is all that’s needed to add this functionality.

The Router Widget

Flutter Navigator 2.0 introduced the `Router` widget to take a more declarative route approach. Its main aim is to help sync the browser URL to the app pages.

The Router Widget Use Cases

The `Router` widget is what will likely come in handy if you’re using Flutter for web app development.

At first, the widget can be challenging to master because it has many essential nested functions that you need to understand, and it requires many more lines of code even for simple navigation compared to the `Navigator` widget.

However, if your app has any type of content and you want to save the history of every single interaction, then you should use the `Router` widget. For example, if you’re building a blog and you want to store the URLs of the user’s visited blog posts from their history. Or, let’s say you showcase videos on your site and you want to store the URLs of videos that users have watched on your website from their history.

How Does It Work?

As we mentioned, the `Router` widget is complicated. In fact, despite Flutter trying to make routing more straightforward with this widget, it’s probably more complex than it needs to be.

The widget contains several subsections, which can be explained as follows:

  • Router: A new widget that dispatches the opening and closing of pages in your Flutter app. It wraps around the `pages` attribute in the `Navigator` widget to automatically modify it upon the app’s state changes.
  • Route name provider: This delegate assists the router in actually understanding which routes to show according to the operating system’s request.
  • Route name parser: Parses the string from `routeNameProvider` into a user-specified data type.
  • Router delegate: This is the main component of the `Router` widget. The `routerDelegate` determines how to rebuild the navigation stack to display the pages.
  • Back button dispatch: Tells the app to rebuild the `Router` to go back after the system back button has been pressed.

In more simple terms , this widget handles navigation in the following way:

  • A user interacts with the app;
  • The interaction modifies the state of the app;
  • The app state notifies the `Router` widget; and
  • The `Router` widget rebuilds the `Navigator` to show the new route.

The implementation of many of the functions is left to the developer. You can read more about Flutter’s complex navigation and routing system.

Navigation Best Practices

Now that you know about implementing navigation, it’s time to learn about some coding standards and best practices.

Named vs. Anonymous Routes

`Navigator.push()` and `Navigator.pop()` are used for anonymous routes, where you define the widget you want to redirect the user to.

Anonymous routes push and remove elements from the top of the stack, and the state of the underlying objects is left untouched.

`Navigator.pushNamed()` is used for named routes where you define a `routes` map containing the URL and the widget linked to it.

Third-Party Libraries

A principal rule of software development is to build upon existing solutions rather than writing everything from scratch.

To minimize repetition and work, you can choose from the thousands of Flutter third-party libraries that are available.

Also, most libraries are pre-tested and verified, so you can be confident that the solution you’ll be using will be one of the best ones. Apart from saving time, third-party libraries are also a huge cost saver.

Minimize Complexity

The end user doesn’t care about what technologies and methods you’ve used to build the app. All the user wants is a functional experience. As a developer, it’s your goal to implement the simplest solutions possible instead of more complex ones for marginal improvements. In short, you should use an elaborate system only if the resulting improvement is huge.

For Flutter navigation, using the `Router` widget is only recommended if your intended use perfectly matches its targeted use cases. Otherwise, for most purposes, the `Navigator` widget will get the job done satisfactorily without introducing a ton of unnecessary complexity.

Conclusion

This article introduced two different methods of implementing navigation in Flutter. You can either use the old, imperative, but more accessible approach of the `Navigator`, or use Navigator 2.0’s new, but more complex procedure, like the `Router`.

It mostly boils down to your particular use case and preference, and ultimately, ensuring that your chosen method provides a great experience for your users is what matters the most!

Lead Photo by Clément Hélardot on Unsplash

Table of Contents

Flutter

Front End

Dart

Widget

More from Pieces
Subscribe to our newsletter
Join our growing developer community by signing up for our monthly newsletter, The Pieces Post.

We help keep you in flow with product updates, new blog content, power tips and more!
Thank you for joining our community! Stay tuned for the next edition.
Oops! Something went wrong while submitting the form.