An app with only one screen isn’t fun, so lets learn about navigation in Flutter. In this post you are going to learn about Navigator, setting up routes and pass information from one screen to another.
Navigation is a key element in apps as you only have limited space and therefor needs to navigate between screens often. We will start be getting the terminology in place. In Flutter screens and pages is called routes. If you already are familiar with developing Android or iOS apps, then you might know that in android a route is an activity and in iOS apps it’s a ViewController. But as everything else in Flutter route is a widget.
- Navigator simple
- Named routes with Navigator
- Pass arguments with named routes
- Navigation for scalable Flutter apps – onGenerateRoute()
Test the different navigation possiblities in your own app, while reading the post. This will help you to understand which option is best suited for your application. If you haven’t already started a Flutter project, you can follow my guide Creating a new Flutter project.
Navigator simple
If it’s your first time using Flutter and you want a simple way to navigate between two screens. Then, you can use this simple code snippet to navigate between two screens.
Navigate to a new screen: Navigator.push()
Navigate back to the previous page: Navigator.pop()
dart
// Within the `FirstRoute` widget
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
}
// Within the SecondRoute widget
onPressed: () {
Navigator.pop(context);
}
This is a really simple way to navigate between screens, but it will result in a lot of duplication if you want to navigate to the same screens in other screens. Furthermore, if your screens widgets are build in different files, then you have to import the file that include the screen you want to navigate to. So, are you building a larger application, then read further on and I will tell you about some other more scalable solutions.
Named Routes with Navigator
Are you building an application containing more than two screens, then named routes is your go to. Here you define the routes, just like when you navigate on a website. “/” leads to the homepage and “/About” lead to your about page. You define the routes in MaterialApp as shown below.
dart
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => FirstScreen(),
'/second': (context) => SecondScreen(),
},
);
InitialRoute defines the home page for the app and routes defines the different screens. When you want to navigate between views you will now replace .push with .pushNamed as seen below. Tip: When using named routes you don’t need to define Home in MaterialApp.
dart
// Within the `FirstScreen` widget
onPressed: () {
Navigator.pushNamed(context, '/second');
}
// Within the SecondScreen widget
onPressed: () {
Navigator.pop(context);
}
If you have many screens/routes, then I will recommend that you create a separate file called router.dart and define your different routes there instead. This will make your code much easier to read. I will explain it in details in the following sektion.
Navigation for scalable Flutter apps
Lets look at how you can setup your navigation in Flutter, so your app is ready for scale. We will be using named routes as explained above, but we will add the property onGenerateRoute().
onGenerateRoute() basically lets you create a function that calls the route settings. It’s called in MaterialApp where it replaces routes and instead call the new router file. Which we are going to create in a bit.
If you have read my previous post, you know that I’m building a Flutter app that will present statistics for my company DecorRaid. We will continue working on this app, for this example. At this state my app only have one screen, statsView. That’s why I have used the simple navigation of just setting Home: StatsView(). Notice how I have to import the stats.view.dart file to be able to navigate to it.
main.dart
import 'package:flutter/material.dart';
import ‘package:DecorRaid_Admin/ui/shared/utils/appTheme.dart’;
import 'package:DecorRaid_Admin/ui/views/stats/stats.view.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: ‘Flutter admin’,
theme: ThemeData(
primaryColor: AppTheme.colors.dustyBlue,
),
home: StatsView(),
);}}
I’m planning that my app should include a lot more screens, as you properly also are for your app. So, to make it scalable from the beginning we are going to use onGenerateRoute().
Setup
But first we need some pages to navigate between. I have my statsView, but I will create a Home view as well. For now I will just create it in the main.dart file and add text widget.
main.dart
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text('Home')),
);}}
If you just created you Flutter project, you might want to have a look at my post about setting up a scalable folder structure.
Routing Setup
Now it’s time to setup routing between your new screens. We will start be creating a new file called router.dart. Within this file we will define the settings for our navigation. If you have followed my example on how to structure your Flutter app folder, then you can add the file to ui > shared > utils.
In the file we are going to create a RoutePaths class. Because we are defining a class we don’t need to add “/” before the name of where we are navigating too.
Furthermore, we are going to define a Router class that defines the function Route<dynamic> and takes in RouteSettings which onGenerateRoute() needs. Within this function we will define the different navigation cases. In our example we have Home and StatsView we want to navigate too. The settings provides two keys we can use, the name and the arguments. In this example we will only use the name as it’s the one used to determine which view to return. We will get back to arguments later in the post.
router.dart
import 'package:flutter/material.dart';
import 'package:DecorRaid_Admin/main.dart';
import 'package:DecorRaid_Admin/ui/views/stats/stats.view.dart';
class RoutePaths {
static const String Home = 'home';
static const String StatsView = 'statsView';
}
class Router {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case RoutePaths.Home:
return MaterialPageRoute(builder: (_) => Home());
break;
case RoutePaths.StatsView:
return MaterialPageRoute(builder: (_) => StatsView());
break;
default:
return MaterialPageRoute(
builder: (_) => Scaffold(
body: Center(
child: Text('No route defined for ${settings.name}')),
));
}
}
}
Now to the last part. Go to main.dart and to your MaterialApp where we will remove home and declare onGenerateRoute and initialRoute instead.
Remember to import your newly created router.dart file. In initialRoute you will declare which view will be your starting view and afterwards pass the generateRoute function to onGenerateRoute.
main.dart
import 'package:flutter/material.dart';
import 'package:DecorRaid_Admin/ui/shared/utils/appTheme.dart';
import 'package:DecorRaid_Admin/ui/shared/utils/router.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter admin',
theme: ThemeData(
primaryColor: AppTheme.colors.dustyBlue,
),
initialRoute: RoutePaths.Home,
onGenerateRoute: Router.generateRoute,
);
}
}
Navigation
All your basic settings for using onGenerateRoute is set, so now when you want to navigate between views, you can use:
Navigator.pushNamed(context, Home);
Navigator.pushNamed(context, StatsView);
Just remember everytime you add a new view you have to add the route in Router.dart.
Pass arguments with named routes
Now we have learned how we can navigate between different screens in Flutter, but what if we want to pass information from one screen to another?
As an example lets make our ViewStats take in a string as a parameter.
stats.view.dart
import 'package:flutter/material.dart';
import 'package:DecorRaid_Admin/ui/shared/widgets/WKpiTracker.dart';
class StatsView extends StatelessWidget {
final String title;
StatsView(this.title);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('$title'),
),
body: Center(
child: WKpiTracker(
kpi: 'USERS',
value: '4000',
)
)
);
}
}
main.dart
class Home extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton(onPressed: (){ Navigator.pushNamed(context, RoutePaths.StatsView, arguments: 'STATS'); },), body: Center(child: Text('Home')), ); } }
router.dart
case RoutePaths.StatsView:
var title = settings.arguments as String;
return MaterialPageRoute(builder: (_) => StatsView(title));
If you tap the floating action button on the Home view you’ll be navigated to the StatsView and see that passed title is there. You can pass any kind of data in the arguments.
I hope you have enjoyed the guide and now know how to handle navigation in Flutter.
If you have any questions or comments please reach out in the comments below.
Happy coding 👩🏽💻