Routing in Flutter is a vast topic as it can be executed in many different ways. Having a logical and simple to navigate routing setup will directly translate into a better user experience. It will also make the code a lot more maintainable for the developers. 

Configuring routing in Flutter, specifically with Navigator 2.0 can be very tedious and time consuming. This is where AutoRoute comes in with its intuitive API and handy code generation that will save you lots of time and effort.

In this lesson you’ll learn how to leverage the simplicity of the AutoRoute and Salomon Bottom Bar packages to create an elegant bottom navigation bar configured with nested routing.

The Finished App

In this tutorial we are going to build a simple app that will have three main sections.

  • Posts
  • Users
  • Settings

The posts section will display some mock post tiles. When you tap on a post tile, you will be taken to the corresponding post page. The users section will have some mock user avatars. When you tap on a user avatar you will be taken to the corresponding user profile page. Lastly, the settings section will only have one page displaying some mock user account information.

These three top-level sections can be navigated through using a minimalistic, customizable bottom navigation bar. Every section is essentially a separate router located within the root router. The posts and users routers have children routes through which you can navigate to individual post pages and user profile pages.

In this tutorial we are going to learn the absolutely simplest way to configure this kind of setup.

Getting Started

Flutter 2.5 Update

On September 8th, 2021, Google's Flutter team announced the release of Flutter 2.5 and Dart 2.14. In this lesson we'll be developing with the updated versions. The provided starter and finished project files are built with the new versions of Dart and Flutter. So, if you haven’t upgraded yet, make sure you go and run the flutter upgrade command in your terminal before continuing with this tutorial.

If for any reason you aren’t ready to upgrade just yet, then don’t worry. You can still follow along with the lesson with some minor adjustments.

  • With the new Flutter version, anytime you create a new project, you'll have the flutter_lints dev dependency included. This will help you write cleaner code right out of the box. If you haven’t upgraded yet, you won’t notice any significant difference in the tutorial other than the flutter_lints dev dependency in the pubspec.yaml file.
  • Some of the dependencies we'll use in this project depend on meta 1.7.0 and if you haven’t upgraded you'll be faced with an issue since the previous version of Flutter is constrained to an earlier version meta. You can easily overcome this by overriding the meta version. Just add the following code to your pubspec.yaml file:


  meta: ^1.7.0
  • If you want to follow along with the tutorial using the starter project files or view the finished project on your device and you haven’t upgraded you should do the following:
    1. Download the project files from GitHub (links provided below).
    2. Create a new Flutter project on your computer. 
    3. Copy the lib folder from the downloaded project and paste it in place of the lib folder in your newly created project. 
    4. Fix the imports.
    5. Make sure you override the meta dependency as mentioned above. 


In this tutorial we'll be using several dependencies and dev dependencies.

For routing, we'll use the auto_route dependency and auto_route_generator dev dependency. Both will be version 2.3.2. The auto route generator will help us generate code that we would otherwise have to write ourselves. This is what’s so great about AutoRoute, it allows us to bypass writing a lot of boilerplate code. To generate the code, we also need to add build_runner version 2.1.2 as a dev dependency.

To create the stylish bottom navigation bar, we'll use the Salomon Bottom Bar package. This package was inspired by the design created by Aurélien Salomon. I chose this package because the design is very clean and appealing and the implementation of this nav bar is incredibly easy. If you’ve ever created a BottomNavigationBar widget in Flutter, then you will already know how to set up the nav bar from the Salomon Bottom Bar package. Their syntaxes are nearly identical. For this project we'll use version 3.1.0 of the package.

Go ahead and add all of these dependencies now. Your pubspec.yaml file should look something like this after you're done:


    sdk: flutter
  auto_route: ^2.3.2
  salomon_bottom_bar: ^3.1.0
  cupertino_icons: ^1.0.2

  auto_route_generator: ^2.3.2
  build_runner: ^2.1.2
If you’re using Visual Studio Code you can now add dependencies using the command palette. With the newly updated VS Code Flutter plugin, you can simply bring up the command palette and use the “Dart: Add Dependency” and the “Dart: Add Dev Dependency” commands to add packages to your projects.

Starter Project Overview

In order to free ourselves from worries of building the majority of the UI, we'll be working with a starter project in this tutorial. To follow along you can grab the starter and finished projects from the GitHub links below.

In the main.dart file we have an AppWidget which returns a MaterialApp. Right now the MaterialApp has the PostsPage widget as the home argument, but this will change once we implement the routing.

Then, in the lib folder we also have the widgets.dart file which has the PostTile and UserAvatar widgets. This is a necessary separation to declutter the pages where these widgets are used. 

Then, we have a data folder with an app_data.dart file inside of it. This file contains Post and User classes. These classes are used to create mock data for the posts and users in the app.

Please note, that the way mock data is passed around in the app is not based on best practices. It's done in a simplified way so we can focus on the routing implementation.

The remaining project files are separated into folders based on app features. There are quite a few files here already, and since we'll be adding more, this structure will help keep things organized.

Posts folder:

In the posts folder we have the posts_page.dart and the single_post_page.dart files.

  • In the posts_page.dart file we have a StatelessWidget which will display a Column with three PostTile widgets. You may notice that this page doesn’t have a Scaffold. When we get to configuring the bottom navigation bar, you'll see why. 
  • The single_post_page.dart contains a StatelessWidget which will dynamically display a page with a post name and post color corresponding to the PostTile that was tapped from the posts page.

Users folder:

In the users folder we have the user_page.dart and user_profile_page.dart files.

  • The users_page.dart contains a StatelessWidget which displays a Column with three UserAvatar widgets. 
  • In the user_profile_page.dart file we have a StatelessWidget that will display a page with a dynamically set background color and username corresponding to the UserAvatar that was tapped from the users page.

Settings folder:

In the settings folder there is only one file - settings_page.dart. This is the simplest out of all the feature files we covered. The stateless SettingsPage widget located here simply displays a text title and some fake user account data.

Now that you have a solid understanding of the starter project, let’s begin implementing the routing. 

Nested Routing Configuration

In this section we are going to configure a file that will provide the blueprint for the generated routing code. If you worked with AutoRoute in the recent past then the syntax here should look pretty familiar. Keep in mind that this configuration will differ from the standard AutoRouter routing setup. That is because we'll follow the specific guidelines for creating a bottom navigation with nested routing.

Initial Route - HomePage

Before we configure the routing, let’s first make sure we have all of the files necessary to do that. Go ahead and create a new file in the lib folder and call it home_page.dart. This will be the file where we define the bottom navigation bar. For now, just create a StatelessWidget here. It doesn’t matter what it returns, because we'll change this in a short while. You can just have it return a Container for now.


class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  Widget build(BuildContext context) {
    return Container();

AutoRoute Configuration - router.dart

Now, create a new folder in the lib folder and name it "routes". In this folder create a new file and name it router.dart. This is the file where we'll create the blueprint for the code generator.

Let’s begin by setting up the router with an initial HomePage route. Don’t forget to import the auto route package and the home_page.dart file.


  replaceInRouteName: 'Page,Route',
  routes: <AutoRoute>[
      path: '/',
      page: HomePage,
class $AppRouter {}

The syntax here might look a bit unusual, but this is what’s required for the code generation to work correctly.

Here we are specifying the replaceInRouteName argument in order to make our route names less redundant. When you navigate from one page to the next, you'll need to use the generated route names. If you don’t specify the replaceInRouteName the way we did here, the route name for our SinglePostPage would be SinglePostPageRoute. With replaceInRouteName configured, the generated route name in this example would instead be SinglePostRoute.

Then we provide a List of AutoRoute objects as the routes argument. Here we’ve got one AutoRoute object which sets our HomePage as the initial route by providing “/“ as the path argument.

Next, we need to create routers for the app’s posts, users and settings sections. To do this, add a List of AutoRoute objects as the children argument of the existing AutoRoute object, like demonstrated below. Here you’ll also need to import all of the page widget files used.


      path: '/',
      page: HomePage,
      children: [
          path: 'posts',
          name: 'PostsRouter',
          page: EmptyRouterPage,
          children: [
            AutoRoute(path: '', page: PostsPage),
            AutoRoute(path: ':postId', page: SinglePostPage),
          path: 'users',
          name: 'UsersRouter',
          page: EmptyRouterPage,
          children: [
            AutoRoute(path: '', page: UsersPage),
            AutoRoute(path: ':userId', page: UserProfilePage),
          path: 'settings',
          name: 'SettingsRouter',
          page: SettingsPage,

Now, let’s dissect what exactly we are doing here.

Posts and Users Routers:

The posts and users routers are set up in the same manner, so let’s discuss all of the components present in their configuration now.

  • path: For the path argument we provide the path name we'd like the router to have.
  • name: The String provided as the name argument will be used to generate the name for the router. This name can be used to access the router to configure the bottom navigation bar, or navigate between pages located in different routers/navigation tabs.
  • page: Here we are providing EmptyRouterPage (provided by the AutoRoute package) as the page argument. You should do this whenever you have nested routes for a specific bottom navigation tab.
  • children: This children argument accepts aList of AutoRoute objects. This will be the List of nested routes that will live under the given router. The first route has an empty String for the path argument. This indicates that this will be the first page to be displayed when you select the corresponding navigation tab. The second AutoRoute object path looks a bit different. The ':postId' and ':userId' syntax is used to create dynamic segments. With this setup, if you are running your app in the browser and enter something like “/posts/1” you'll be taken to the page for the post with the postId field equal to 1. For this to work correctly, you also need to annotate the postId and userId constructor parameters in the page files. We'll do this soon.


In the settings router the main differences are that there are no children and the page argument is set to SettingsPage instead of EmptyRouterPage. This is because we don't have any nested routes here, and in this case we should just set the page argument to the page we want displayed.

Before creating the generated code file, let’s do one more thing. As mentioned earlier, in order for the dynamic segments defined as ':postId' and ':userId' to work we need to head over to the single_post_page.dart and user_profile_page.dart files and annotate the corresponding constructor parameters with @PathParam('optional-alias'). If you define an alias, it should match the segment name you defined in the router.dart file. If your field name matches the segment name, then you don't need to provide an alias. Go ahead and do this for SinglePostPage first.


const SinglePostPage({
  Key? key,
  @PathParam() required this.postId,
}) : super(key: key);

Since our postId  field name matches the segment name defined in the router.dart file we didn't include the alias in the annotation. Now you can do the same thing for the UserProfilePage.


const UserProfilePage({
  Key? key,
  @PathParam() required this.userId,
}) : super(key: key);

To create a file using code generation from the blueprint we created, run the following terminal command.


flutter pub run build_runner build --delete-conflicting-outputs

We are using the build flag which will cause the generator to run only once. If instead you anticipate making several changes to your router.dart file, then you can swap the build flag for watch. Using watch will run the generator anytime you make changes.

Now you should see a file inside of the routes folder. If you open it up you can see how many lines of code this helpful generation tool saved us from writing.

Linking the Router to the App

Now that we’ve configured the router, we can connect it to our app. Head over to the main.dart file and over there we need to change a few things. Right now the AppWidget returns a MaterialApp. We need to swap it for MaterialApp.router. You can go ahead, delete all of the code inside of the AppWidget build method, and configure your main.dart file in the following way.


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

class AppWidget extends StatelessWidget {
  AppWidget({Key? key}) : super(key: key);
  final _appRouter = AppRouter();
  Widget build(BuildContext context) {
    return MaterialApp.router(
      debugShowCheckedModeBanner: false,
      title: 'Bottom Nav Bar with Nested Routing',
      routerDelegate: _appRouter.delegate(),
      routeInformationParser: _appRouter.defaultRouteParser(),

Now, let’s discuss what we’ve got here. First, we initialized the AppRouter and stored it inside an _appRouter variable. Because of that we also had to remove the const next to the AppWidget in the runApp and in the constructor. The AppRouter is generated by AutoRoute for us, so make sure to import the file here. By initializing the AutoRoute inside of the root widget, we make this router accessible across the entire app throughout the app’s lifecycle.

Then we provided two mandatory, routing specific arguments to the MaterialApp.router. The values we provided for the routerDelegate and routeInformaitonParser arguments come from the generated AppRouter object.

That’s it, we now have all of the necessary configurations in place. Next, we are going to start implementing the bottom navigation.

Implementing the Bottom Navigation


We can finally start implementing the bottom nav bar for the app. Luckily, the AutoRoute package has a helpful widget which makes it incredibly simple to configure this. Open the home_page.dart file we created earlier and replace the Container in the build method with an AutoTabsScaffold widget. This widget comes from the AutoRoute package, so make sure to import it here.


class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  Widget build(BuildContext context) {
    return AutoTabsScaffold();

The AutoTabsScaffold widget allows us to easily create a Scaffold with tabbed routing. This Scaffold will persist throughout the app. We want our app to have an app bar and a bottom navigation bar.

If you take a look, you may notice that unlike the PostsPage,
the SinglePostPage has its own Scaffold. This is because we want the PostsPage to have the Scaffold defined through the AutoTabsScaffold widget. In the SinglePostPage and the UserProfilePage however, we defined a separate Scaffold to be able to specify a custom background color.

First, let’s create an app bar. For this, we can use the appBarBuilder callback that will return an AppBar widget.


return AutoTabsScaffold(
  appBarBuilder: (_, tabsRouter) => AppBar(
    backgroundColor: Colors.indigo,
    title: const Text('FlutterBottomNav'),
    centerTitle: true,
    leading: const AutoBackButton(),

The appBarBuilder callback gives us access to the context and a TabsRouter object. We won’t use them for this app bar, though. Our app bar has a custom background color, centered title, and an AutoBackButton as the leading argument. The AutoBackButton is a widget provided by the AutoRoute package to easily handle nested router popping. We will see it in action shortly.

Next, let’s give our AutoTabsScaffold a custom background color and specify the routers we want included in the bottom navigation bar. To do this we will provide a list of routers we created earlier to the routes argument in the order we want the corresponding navigation tabs to be displayed.


backgroundColor: Colors.indigo,
routes: const [

Now we can configure the bottom navigation bar itself. For this we will use the bottomNavigationBuilder argument.


bottomNavigationBuilder: (_, tabsRouter) {},

This callback gives us access to the context and a TabsRouter object. We will need to use the TabsRouter object here. You can use this callback to return the BottomNavigationBar widget that is included with Flutter, but you can also return a custom navigation bar as well. To demonstrate this, we will use the Salomon Bottom Bar package to create the bottom nav.

Salomon Bottom Bar

If you’ve ever used the BottomNavigationBar widget that ships with Flutter, then you will find the SalomonBottomBar incredibly intuitive to configure. First, we need to import the Salomon Bottom Bar package into the home_page.dart file. Then we need to return the SolomonBottomBar widget from the bottomNavigationBuilder callback. Once this is in place, we need to specify the following arguments for the SalomonBottomBar widget:

  • margin: creates some spacing around the navigation tabs. This is optional of course.
  • currentIndex: index of the current navigation tab 
  • onTap: a function that returns the index of the tab that was tapped
  • items: a List of SolomonBottomBarItem widgets, one widget for every navigation tab in the bottom nav.

The SolomonBottomBarItems require their own setup. For our app we will provide values for the following arguments:

  • selectedColor
  • icon
  • title

Once all of this is done the SolomonBottomBar widget should resemble the code snippet below.


return SalomonBottomBar(
  margin: const EdgeInsets.symmetric(
    horizontal: 20,
    vertical: 40,
  currentIndex: tabsRouter.activeIndex,
  onTap: tabsRouter.setActiveIndex,
  items: [
      selectedColor: Colors.amberAccent,
      icon: const Icon(
        size: 30,
      title: const Text('Posts'),
      icon: const Icon(
        size: 30,
      title: const Text('Users'),
      selectedColor: Colors.pinkAccent[100],
      icon: const Icon(
        size: 30,
      title: const Text('Settings'),

As you can see, this required a minimal amount of work. There are lots of other ways you can customize the bottom nav, but for this simple example we will stick with what we have now.

Now you can run the app and see the bottom navigation bar in action. When you tap on the bottom nav bar tabs, you should be navigated to the corresponding pages. You will also see the sleek navigation bar with animations when tapping on the tabs.

Navigating to SinglePostPage & UserProfilePage

Right now we can comfortably navigate to the PostsPage, UsersPage, and SettingsPage using the bottom nav. What we are missing though, is the ability to navigate to the SinglePostPage and UserProfilePage routes when tapping on the post tiles and user avatars.

AutoRoute provides many different methods for you to be able to navigate around your app. In this example we will stick to the simple push method. To call the push method or any of the other navigation methods, you first need to get the scoped router by calling either AutoRouter.of(context) or context.router. Then you can call the method of your choice on the scoped router and pass the desired route(s) to it. Head over to the posts_page.dart file and set up the routing to the SinglePostRoute by calling the push method in the onTileTap argument.


child: Column(
  children: [
    for (int i = 0; i < posts.length; i++)
        tileColor: posts[i].color,
        postTitle: posts[i].title,
        onTileTap: () => context.router.push(
            postId: posts[i].id,

Now, let’s move over to users_page.dart and do the same kind of thing for the UserProfileRoute in the onAvatarTap argument.


child: Column(
  children: [
    for (int i = 0; i < users.length; i++)
        avatarColor: users[i].color,
        username: 'user${users[i].id}',
        onAvatarTap: () => context.router.push(
            userId: users[i].id,
In this app we are navigating to routes that are located in the same router. If you want to navigate from a page in one navigation tab/router to a page in another navigation tab/router you can do that as well. Suppose you wanted to navigate from a UserProfileRoute to SinglePostRoute, you could do it like this:
context.navigateTo(PostsRouter(children: SinglePostRoute(postId: id))).

Now you can restart the app and try out all the navigation we’ve set up. Note that when tapping on the post tiles and user avatars, you should see a back button appear in the Scaffold. That is because earlier we added an AutoBackButton widget as the leading argument of the AutoTabsScaffold.


That’s all, our app is complete! Just the time we saved by using AutoRouter makes this a no-brainer of an approach when it comes to creating a bottom navigation bar. You should now be able to use what you’ve learned here in your own projects and customize everything for your individual use cases.

About the author 

Ashley Novik

Ashley is a Flutter developer and tutor at Reso Coder with a passion for tech and an infinite drive to learn and teach others ?.
On her days off she enjoys exploring nature and powering through off-road trails on her mountain bike ?‍♀️.

You may also like

  • Great tutorial, much thanks.
    One question, How would you go about if you want the bottom bar onTap event to always send you to the root page of the section your in?
    For example, if you’re on a single post page and you press the post item in the bottom nav it will always take you to the posts list page.

    • Hi Abdullah,

      Thanks for the positive feedback!

      I’m not sure if this is the most efficient way to do this, but I managed to achieve the functionality you’re looking for by popping routes until the root route in a specific tab if the tab index you’re selecting is the the same as the index for the currently active tab. Like so:

      onTap: (index) => index == tabsRouter.activeIndex
      ? tabsRouter.stackRouterOfIndex(index)?.popUntilRoot()
      : tabsRouter.setActiveIndex(index),

      Hope this helps!

      • Thanks Ashley!

        popUntilRoot() is exactly what I needed. In my case I want it to go to the root regardless, so i wrote mine like this:

        onTap: (index) {

  • Awesome tutorial, but any idea how to navigate across different router for child widget? For example, if I want to link a specific user page to be accessed through the single post page?

    I’ve tried to do that and it raised an exception:
    Error: [PostsRouter Router] Router can not navigate to UserProfilePage

    • Sorry, I just realized the context.navigateTo part, it did work for me like the following

      context.navigateTo(UsersRouter(children: UserProfilePage(userId: 1)));

  • Base on the example…..Suppose you wanted to navigate from a UserProfileRoute to SinglePostRoute, you could do it like this:

    context.navigateTo(PostsRouter(children: SinglePostRoute(postId: id))).

    The problem is, when I click the back button on SinglePostRoute, I am redirecting to its parent PostsRouter, not on UserProfileRoute

    Any solution to this, you can reproduct the problem on your own example

  • Hi! Ty! Nice tutorial, but i am having hero animation problems! Doesn’t work with your approach! the animation twitches and runs to the end

  • [INFO] Generating build script…
    [INFO] Generating build script completed, took 481ms

    [INFO] Initializing inputs
    [INFO] Reading cached asset graph…
    [INFO] Reading cached asset graph completed, took 67ms

    [INFO] Checking for updates since last build…
    [INFO] Checking for updates since last build completed, took 747ms

    [INFO] Running build…
    [INFO] Running build completed, took 14ms

    [INFO] Caching finalized dependency graph…
    [INFO] Caching finalized dependency graph completed, took 39ms

    [SEVERE] auto_route_generator:autoRouteGenerator on lib/router.dart (cached):

    Route must have either a page or a redirect destination
    [SEVERE] Failed after 73ms
    pub finished with exit code 1

  • Running from the final code…

    Running Gradle task ‘assembleDebug’…
    ../../development/flutter/.pub-cache/hosted/ Error: Required named parameter ‘type’ must be provided.

    • I had to remove the dependency override in pubspec.yaml and then update all modules to the latest version ‘flutter pub upgrade –major-versions’.
      afterwards run the flutter build_runner command again.

  • How I can achieve following condition:
    I want to navigate to Profile section on click of the Post1 card from home page, and also the selected buttom navigation bar will be the profile page

  • Thank you for the amazing lesson, i followed your instructions yet when i run this command in the terminal I get this error

    ” Duplicate route names must have the same path! (name: MainRouter, path: auth)
    Note: Unless specified, route name is generated from page name.

    72 │ class $AppRouter {}
    │ ^^^^^^^^^^

  • Great tutorial, much thanks.
    One question, How could you use AutoRouteObserver to detect tab change. Regards.

  • I do not even know how I ended up here, but I thought this post was great. I do not know who you are but certainly you’re going to a famous blogger if you are not already 😉 Cheers!

  • I loved as much as you will receive carried out right here. The sketch is attractive, your authored material stylish. nonetheless, you command get got an impatience over that you wish be delivering the following. unwell unquestionably come more formerly again since exactly the same nearly a lot often inside case you shield this hike.

  • What i do not realize is in fact how you are no longer actually much more well-favored than you might be right now. You’re very intelligent. You recognize thus considerably in relation to this topic, made me in my view believe it from numerous numerous angles. Its like men and women are not fascinated until it is one thing to do with Lady gaga! Your own stuffs excellent. All the time handle it up!

  • Its like you read my mind! You appear to know so much about this, like you wrote the book in it or something. I think that you can do with a few pics to drive the message home a little bit, but other than that, this is fantastic blog. A great read. I’ll certainly be back.

  • Normally I do not read article on blogs, however I would like to say that this write-up very forced me to try and do so! Your writing style has been amazed me. Thanks, quite great post.


    My name is Aziz Badawi, I’m 27 year old man from Palestine. Our town has recently evacuated due
    to bombardments by Israel, now I am staying at a shelter with my 6 year old daughter Nadia. My wife is
    dead and I’m the only one left to take care of my daughter as we are not allowed to depart to my parents house
    in Nablus, she is sick with a congenital heart defect and I have no way to afford the medicine she needs anymore.
    People here at the shelter are much in the same conditions as ourselves…

    I’m looking for a kind soul who can help us with a donation, any amount will help even 1$ we will
    save money to get her medicine, the doctor at the shelter said if I can’t find a way to get her the
    medication she needs her little heart may give out during the coming weeks.

    If you wish to help me and my daughter please make any amount donation in bitcoin cryptocurrency
    to this bitcoin wallet: bc1qcfh092j2alhswg8jr7fr7ky2sd7qr465zdsrh8

    If you cannot donate please forward this message to your friends, thank you to everyone who’s helping me and my daughter.

  • Modern Talking был немецким дуэтом, сформированным в 1984 году. Он стал одним из самых ярких представителей евродиско и популярен благодаря своему неповторимому звучанию. Лучшие песни включают “You’re My Heart, You’re My Soul”, “Brother Louie”, “Cheri, Cheri Lady” и “Geronimo’s Cadillac”. Их музыка оставила неизгладимый след в истории поп-музыки, захватывая слушателей своими заразительными мелодиями и запоминающимися текстами. Modern Talking продолжает быть популярным и в наши дни, оставаясь одним из символов эпохи диско. Музыка 2024 года слушать онлайн и скачать бесплатно mp3.

  • Empower Your Farm with Bwer Pipes’ Irrigation Solutions: Bwer Pipes offers a comprehensive range of irrigation products designed to empower farmers in Iraq. From efficient sprinkler systems to durable pipes, our solutions are engineered to enhance water distribution, promote crop growth, and maximize yields, ensuring agricultural success. Visit Bwer Pipes

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}