Introduction
Routes is an integral part of Navigation which in turn is an important aspect of flutter application. Routes are unique paths to a screen in a flutter app which is used to navigate in the app.
Let’s create a new flutter app called navigation_app
. Open your own terminal and run following command to create,
flutter create navigation_app
Open this project in vscode(or your preferred editor), and create two files screen_1.dart
and screen_2.dart
.We will create a normal screen with a appbar and center text as follows:-
//screen_1.dart
import 'package:flutter/material.dart';
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: Center(
child: Text('Main Screen'),
)
);
}
}
and screen_2.dart
will contain:-
//screen_2.dart
import 'package:flutter/material.dart';
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Screen'),
),
body: Center(
child: Text('Second Screen'),
)
);
}
}
Now,we will set the FirstScreen
as our main screen by editing main.dart
file:-
import 'package:flutter/material.dart';
import './screen_1.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: FirstScreen(),
);
}
}
If you run this app on emulator or device, you will see something like this,
Let’s create show sort of input(button in this case) which will tell flutter that it’s time to change screen. Edit screen_1.dart
to create button,
import 'package:flutter/material.dart';
class FirstScreen extends StatelessWidget {
void showSecondScreen() {
//we will do something here
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: Center(
child: RaisedButton(
child: Text('Go to Second Screen'),
onPressed: showSecondScreen
),
)
);
}
}
We are creating a button of type RaisedButton
and linking it to showSecondScreen
function.Let’s discuss possible ways of creating link betwen pages and why i don’t like them.
Method 1
Flutter provides Navigator
to navigate between pages, it require context
and a route type.Our function showSecondScreen
do not accept any context till now, so change the function definition as follows:-
void showSecondScreen(BuildContext ctx) {
//we will do something here
}
and change the function call in RaisedButton
component to reflect the change
RaisedButton(
child: Text('Go to Second Screen'),
onPressed: () {
showSecondScreen(context);
}
)
Since our app is material type, We will use MaterialPageRoute
which return a builder function with its own context. As we do not need its context,use _
notation to signify that you do not need the context. Then our showSecondScreen
function will look like:-
void showSecondScreen(BuildContext ctx) {
Navigator.of(ctx).push(
MaterialPageRoute(builder: (_) {
})
);
}
We need to return Widget
in builder function. That is where we will use SecondScreen
page. Import the widget from screen_2.dart
, and update showSecondScreen
function,
void showSecondScreen(BuildContext ctx) {
Navigator.of(ctx).push(
MaterialPageRoute(builder: (_) {
return SecondScreen();
})
);
}
Our screen_1.dart
will look something like this,
import 'package:flutter/material.dart';
import './screen_2.dart';
class FirstScreen extends StatelessWidget {
void showSecondScreen(BuildContext ctx) {
Navigator.of(ctx).push(
MaterialPageRoute(builder: (_) {
return SecondScreen();
})
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: Center(
child: RaisedButton(
child: Text('Go to Second Screen'),
onPressed: () {
showSecondScreen(context);
}
),
)
);
}
}
If we now click on button, it will take us to secondScreen
and we will get following screen:-
What’s wrong with this approach?
-
We need to import component which we need to link in our current screen. It might look good for one link. But if your app is large, it become a pain to list of imports in every other page.
-
You need to know whether your widget is MaterialApp-based or Cupertino-based asyou have
MaterialPageRoute
orCupertinoPageRoute
.
Method 2
Flutter provides a alternate method to use screens which is declaring routes in main.dart
. We can declare routes in as follows:-
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: FirstScreen(),
routes: {'/second_screen': (ctx) => SecondScreen()},
);
}
}
Here, /second_screen
is the unique url to identify screens. You can also use something like abc
, i used /
due to web-like routes.
Edit showSecondScreen
to reflect the same change to create the link,
void showSecondScreen(BuildContext ctx) {
Navigator.of(ctx).pushNamed('/second_screen');
}
What’s the improvement in this method?
- Now you only need to import all the components in one place, i.e.,
main.dart
instead of every component which need link.
What’s wrong with this approach?
- You need to remember all the unique strings and their corresponding components which is prone to mistake. One typo and your app will break. In bigger app, it will be a big problem.
Method 3
We can store corresponding url in the component itself. Open and edit screen_2.dart
as follows:-
class SecondScreen extends StatelessWidget {
static const String url = '/second_screen';
//rest is same
}
Now you can edit main.dart
as follows:-
routes: {SecondScreen.url: (ctx) => SecondScreen()}
Also import SecondScreen
in screen_1.dart
and edit showSecondScreen
void showSecondScreen(BuildContext ctx) {
Navigator.of(ctx).pushNamed(SecondScreen.url);
}
What’s the improvement in this method?
You do not need to remember any url,editor will do it for you. Plus you do not need to remember if it’s a MaterialPageRoute
or CupertinoPageRoute
.
What’s wrong with this approach?
- You need to import every component in
main.dart
as well as other files where you need them.
Pro’s Method
Flutter right from the beginning is heavily based on classes and Enums. Method 3 needs a number of imports in main.dart
as well as all the files where you may need to link them.
Create a new file as routes.dart
and add following code:-
enum Routes {
SecondScreen
}
extension RoutesExtension on Routes {
static const route_list = {
Routes.SecondScreen: '/second_screen'
};
String get route => route_list[this];
}
Here we are declaring a enum called Routes
and then we are mapping all the possible values of enum with corresponding url, SecondScreen
is being mapped with '/second_screen'
. Then, we also initialised a getter with name route
which will return mapped value.
Now, add import of ./routes.dart
in main.dart
and use the enum which result in following code,
import 'package:flutter/material.dart';
import 'package:navigation_app/screen_2.dart';
import './screen_1.dart';
import './routes.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: FirstScreen(),
routes: {
Routes.SecondScreen.route: (ctx) => SecondScreen()
},
);
}
}
Similarly your screen_1.dart
will become,
import 'package:flutter/material.dart';
import './routes.dart';
class FirstScreen extends StatelessWidget {
void showSecondScreen(BuildContext ctx) {
Navigator.of(ctx).pushNamed(Routes.SecondScreen.route);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: Center(
child: RaisedButton(
child: Text('Go to Second Screen'),
onPressed: () {
showSecondScreen(context);
}
),
)
);
}
}
Now you can remove the url parameter from screen_2.dart
import 'package:flutter/material.dart';
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Screen'),
),
body: Center(
child: Text('Second Screen'),
)
);
}
}
The final source code of this project is also available on github.