Tutorial
Introduction to Navigation in Flutter
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.
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.
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.
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.
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.