Engineering

04 January, 2022

Flutter Navigator 2.0 Made Easy with Auto Router

If you are a Flutter developer you might have heard about or even tried the “new” way of navigating with Navigator 2.0, which might be one of the most controversial APIs I have seen.

António Valente

Software Engineer

Flutter Navigator 2.0 Made Easy with Auto Router - Coletiv Blog

If you are a Flutter developer you might have heard about or even tried the "new" way of navigating with Navigator 2.0, which might be one of the most controversial APIs I have seen. So controversial that here at Coletiv we're still using Navigator 1.0 in our current projects. But this might change soon!

So what is the Navigator 2.0 API?

In the beginning, navigation between screens in Flutter was done the imperative way, using push, pop, and friends from Navigator 1.0 and it was perfectly fine for most mobile applications. That's one of the reasons we're still doing things that way, since we don't use Flutter for developing Web applications (yet 🔥).

Why do I mention Web applications, you might ask. Well, the "old" way of navigating the screens was ok for most mobile apps, but for apps with more complex navigation use cases that require to have a history stack, like most Web apps are, it kind of falls short.

Like you can see in the design document, Navigator 2.0 aims to:

📖 Introduce a declarative API to set the history stack of the Navigator and a new Router widget to configure the Navigator based on app state and system events.

This certainly sounds good, but why is this API so controversial? The short answer is that it is much more complex and requires lots of code to set up! The Flutter team even started an API usability research! We have now dozens of packages trying to solve this mess and make developers' life a bit easier.

Trying Navigator 2.0 with AutoRoute

We tried vanilla Navigator 2.0 and a couple of packages, but it never felt right to us, until we found AutoRoute.

💡 It’s a Flutter navigation package that allows for strongly-typed arguments passing, effortless deep-linking and it uses code generation to simplify routes setup, with that being said it requires a minimal amount of code to generate everything needed for navigation inside of your App.

Setup

Main Router

First, you need to create your main router class, that will contain a reference to all the pages that can be a route destination.

TIP: If your app has lots of pages it's probably a good idea to separate common pages in separate files like this:

const settingsRoute = AutoRoute( name: 'SettingsRouter', path: '/settings', page: SettingsWrapperPage, children: [ // Path '' means that this will be the initial page of SettingsRouter AutoRoute(path: '', page: SettingsPage), AutoRoute(path: 'profile', page: ProfilePage), AutoRoute(path: 'about', page: AboutPage), ], ); part 'main_router.gr.dart' @MaterialAutoRouter( // Your routes will be called like HomeRoute for Homepage. replaceInRouteName: 'Page,Route', routes: <AutoRoute>[ AutoRoute(page: HomePage, initial: true), settingsRoute, ], ) class MainRouter extends _$MainRouter{}

Generating Routes

Then you need to run build_runner to generate all the navigator boilerplate code.

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

Finishing Setup

Finally, you just need to edit your MaterialApp widget to hook everything using the MaterialApp.router constructor that will indicate that we're using Navigator 2.0.

class App extends StatelessWidget { App({Key? key}) : super(key: key); final _mainRouter = MainRouter(); @override Widget build(BuildContext context) { return MaterialApp.router( routeInformationParser: _mainRouter.defaultRouteParser(), routerDelegate: _mainRouter.delegate(), ... ); } }

Navigating

Navigation Actions

Now you can use push, pop, and friends to navigate using both the generated routes or paths.

AutoRouter.of(context).push(const HomeRoute()); AutoRouter.of(context).pushNamed('/settings'); AutoRouter.of(context).pushAll([const SettingsRoute(), const AboutRoute()]);

Be sure to check the documentation for more methods!

Arguments

AutoRoute will automatically detect your pages' arguments and generate routes that will handle everything for you, including path/query params. That's awesome!

Returning Results

Returning results is done the same way as we're used by now, using the pop completer AutoRouter.of(context).pop(result);.

Complex Use Cases

Nested Navigation

In the settingsRoute example above, which might have seemed a bit complex when first read, we're defining a nested route. SettingsRoute, ProfileRoute and AboutRoute are nested inside a new router called SettingsRouter.

This means that we will have a different router for each parent route, besides the root or main router.

Like said above, this is another reason that is a good idea to have routers in different files.

You might also be wondering what is the SettingsWrapperPage. As the name points out, it's a wrapper for those nested routes. We can use that page to wrap our SettingsRouter sub-routes inside a Scaffold if you want to share some part of the UI across all sub-routes, or a Provider or anything you want really. If you don't need to wrap your routes you can just return AutoRouter() inside the SettingsWrapperPage widget.

This is very important as the AutoRouter widget is required to render the Settings, Profile, and About sub-routes.

Here are some examples:

class SettingsWrapperPage extends StatelessWidget { const SettingsWrapperPage({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: SettingsAppBar(), body: const AutoRouter(), ); } }

In this case, we are sharing SettingsAppBar across all sub-routes that will only be rendered inside the Scaffold body.

class SettingsWrapperPage extends StatelessWidget { const SettingsWrapperPage({Key key}) : super(key: key); @override Widget build(BuildContext context) { return const AutoRouter(); } }

If you don't need to wrap anything you can just do this. You probably want to create a separate widget called EmptyRouterPage for this purpose to share some code if this is a common use case for you.

⚠️ Keep in mind that routing controllers are context-scoped so you must be careful when navigating across different routers.

For example, if you want to navigate the user from the Home page into the About page you should do this:

AutoRouter.of(context).push( SettingsRouter( children: [ AboutRoute(), ], ), );

If you just want to navigate to the Settings page you can simply do AutoRouter.of(context).push(SettingsRouter()); because we've said above that the Settings page will be the initial route of SettingsRouter() by giving it an empty path.

Declarative Routing

If we want to go further with our SettingsWrapperPage we can declaratively render our routes. This is super useful if you want to navigate depending on your app state as AutoRoute will take care of pushing and popping the routes eliminating the need to call push pop and friends directly.

Here's a quick glimpse of this magic:

class HomeWrapperPage extends StatefulWidget { HomeWrapperPage({Key? key}) : super(key: key); @override _HomeWrapperPageState createState() => _HomeWrapperPageState(); } class _HomeWrapperPageState extends State<HomeWrapperPage> { bool _shouldShowPromotion = false; @override void initState() { super.initState(); Timer(const Duration(seconds: 1), () { setState(() { _shouldShowPromotion = true; }); }); } @override Widget build(BuildContext context) { return AutoRouter.declarative( routes: (context) => [ const HomeRoute(), if(_shouldShowPromotion) const PromotionRoute(), ], ); } }

If part of your app flow is state-dependent be sure to check this! A common use case is an authentication flow.

Finishing Thoughts

If you've experienced the same steep learning curve of Navigator 2.0 or simply don't like the API be sure to check AutoRoute.

It is really awesome and, once you understand it, it's easy to set up your app navigation with all the bells and whistles of Navigator 2.0 and even throws in nice things that I didn't mention like RouteGuards to protect your routes or AutoTabsRouter and AutoTabScaffold to handle tab navigation.

Here at Coletiv, we're definitely considering using it to handle the navigation of our Flutter apps so be sure to check the pub.dev page and the documentation to know more about it and give it a try yourself.

Flutter

Navigator 2.0

Auto Route

Join our newsletter

Be part of our community and stay up to date with the latest blog posts.

Subscribe

Join our newsletter

Be part of our community and stay up to date with the latest blog posts.

Subscribe

You might also like...

Go back to blogNext
How to support a list of uploads as input with Absinthe GraphQL

Engineering

26 July, 2022

How to support a list of uploads as input with Absinthe GraphQL

As you might guess, in our day-to-day, we write GraphQL queries and mutations for Phoenix applications using Absinthe to be able to create, read, update and delete records.

Nuno Marinho

Software Engineer

Enabling PostgreSQL cron jobs on AWS RDS - Coletiv Blog

Engineering

04 November, 2021

Enabling PostgreSQL cron jobs on AWS RDS

A database cron job is a process for scheduling a procedure or command on your database to automate repetitive tasks. By default, cron jobs are disabled on PostgreSQL instances. Here is how you can enable them on Amazon Web Services (AWS) RDS console.

Nuno Marinho

Software Engineer

An intro to Svelte for ReactJS developers - Coletiv Blog

Engineering

21 October, 2021

An intro to Svelte for ReactJS developers

After playing around with Svelte and doing some projects, in this blog post Rui shares his experience with and how different it is from React.

Rui Sousa

Software Engineer

Go back to blogNext