While this tutorial has content that we believe is of great benefit to our community, we have not yet tested or edited it to ensure you have an error-free learning experience. It's on our list, and we're working on it! You can help us out by using the "report an issue" button at the bottom of the tutorial.

One of the most fundamental aspects of a mobile app is for the user to be able to move between different pages. Luckily for us, Flutter makes creating routes and moving between screens incredibly easy, especially when compared to many front-end solutions.

Project File Setup

For our example, we’re just going to have 4 screens, our main.dart file, and break the navbar into its own file.

* screens 📂 * account_screen.dart * balance_screen.dart * transfer_screen.dart * welcome_screen.dart * main.dart * navbar.dart

Naming Routes

While you would want to break each route into its own file in most cases, we’ll put them in our main.dart for now.

In our MaterialApp we can set the routes map, which is a list of key/value pairs. Each item in this map links a string value to a callback function that returns the page we want rendered. The point of this is to speed up development by letting us toss around something like 'welcome_screen' whenever we need a new page, instead of the full (context) => WelcomeScreen() .

To set our home page we can either use the MaterialApp ’s home property or the initialRoute property. They effectively do the same thing but home takes the class itself, like WelcomeScreen() , and initialRoute takes the key from our routes map. You can’t use both since that confuses the compiler.

main.dart

import 'package:flutter/material.dart'; import './screens/welcome_screen.dart'; import './screens/account_screen.dart'; import './screens/balance_screen.dart'; import './screens/transfer_screen.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Navigation Demo', home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return MaterialApp( home: WelcomeScreen(), routes: { 'welcome_screen': (context) => WelcomeScreen(), 'account_screen': (context) => AccountScreen(), 'balance_screen': (context) => BalanceScreen(), 'transfer_screen': (context) => TransferScreen() }); } }

That works fine, but you may end up typing each of these routes often and just using strings will make it hard to debug when you make the slightest typo. Instead it would make our code a bit less fragile to store each key in a static id variable in each class and just access that id. This will also give us the benefit of VSCode’s IntelliSense and help figuring out why a page may be unavailable.

Each screen in our example is the same, beside the id and the text widget. We’re also setting out bottom navbar to a widget that we’ll create later.

welcome_screen.dart

import 'package:flutter/material.dart'; import '../navbar.dart'; class WelcomeScreen extends StatelessWidget { static const String id = 'welcome_screen'; @override Widget build(BuildContext context) { return Scaffold( body: Center( bottomNavigationBar: Navbar(), child: Text('Welcome'), ), ); } }

Now we can replace our string with each screen’s id. Notice that we’re accessing it without actually calling the class itself.

main.dart

class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return MaterialApp(initialRoute: WelcomeScreen.id, routes: { WelcomeScreen.id: (context) => WelcomeScreen(), AccountScreen.id: (context) => AccountScreen(), BalanceScreen.id: (context) => BalanceScreen(), TransferScreen.id: (context) => TransferScreen() }); } }

Push and Pop

Unlike with front-end web development, mobile routing is based on ‘stacking’ screens on top of each other. When we navigate from the welcome screen to the account screen, we’re not really changing pages but adding our account screen onto our stack, thus covering the previous page. To go back to the welcome screen, we would just need to destroy, or pop off, the uppermost layer revealing the already rendered page beneath it.

There are quite a few different methods on Navigator to do this, which you can fully explore here. The main two we need are pushNamed to add to our stack and pop to remove the latest layer. pop just needs our build’s context and push methods needs the context and the page’s key we’ve setup in our routes.

Any method appended with Named is for when we’ve set up our routes in the MaterialApp , otherwise you could pass in the callback itself instead of our keys.

navbar.dart

import 'package:flutter/material.dart'; import './screens/account_screen.dart'; import './screens/balance_screen.dart'; import './screens/transfer_screen.dart'; class Navbar extends StatelessWidget { @override Widget build(BuildContext context) { return Container( color: Colors.red, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[ FlatButton( onPressed: () => Navigator.pop(context), child: Icon(Icons.arrow_left, color: Colors.white, size: 40)), FlatButton( onPressed: () => Navigator.pushNamed(context, BalanceScreen.id), child: Icon(Icons.account_balance, color: Colors.white)), FlatButton( onPressed: () => Navigator.pushNamed(context, TransferScreen.id), child: Icon(Icons.sync, color: Colors.white)), FlatButton( onPressed: () => Navigator.pushNamed(context, AccountScreen.id), child: Icon(Icons.account_circle, color: Colors.white)), ], ), ); } }

Conclusion

Yet again when it comes to routing and navigation Flutter really shines in efficiency and ease of use. Hopefully this short tutorial was helpful in understanding this new technology.