Most apps have several screens and require us to navigate from one screen to another and back. In flutter each screen or page is just a widget, and in context of navigation, each screen or page is called a route. It is equivalent to Activity in Android or ViewController in iOS.
The Navigator class provided by Flutter is used for navigating between screens.
The root of the app
When we start the app, the home widget of the MaterialApp becomes the root of the app, the bottom route of the Navigator stack. When another route is pushed, it is added to the top of the stack.
void main() { runApp(MaterialApp(home: FirstScreen())); }
I am creating here, 2 routes, or 2 screens/pages for our app for this example.
class FirstScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('First Screen'), ), body: Center( child: RaisedButton( child: Text('Next Screen'), onPressed: () { // Navigate to second screen. }, ), ), ); } } class SecondScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Second Screen"), ), body: Center( child: RaisedButton( onPressed: () { // Navigate back to first screen. }, child: Text('Back to First screen'), ), ), ); } }
Navigating to a new Screen
For navigating to Screen 2 from Screen 1, we write the following code in the action; in this example, we add this code in the onPressed() function of the RaisedButton widget.
Navigator.push(context, MaterialPageRoute( builder: (context) => SecondScreen() ) );
Passing data while navigating to a new screen
Often we need to pass some data to the new screen. This is pretty simple to achieve in three simple steps.
First, we declare the variable(s) in the receiving screen widget for the data to be transferred.
Second, create a constructor for the receiving screen widget that accepts the data in its arguments.
final int myInt; SecondScreen({this.myInt});
Third, make the push.
Navigator.push(context, MaterialPageRoute( builder: (context) => SecondScreen(myInt: 5) ) );
Going back from a screen
For returning to the previous screen, simply use pop() function. It removes the screen from the Navigator stack.
Navigator.pop(context);
Returning a value when going back
Sometimes we may need to return a value back to the previous screen, for that we need to modify the push() function in the first screen to wait for a value, using await, and pass the value in the pop() function of the second screen.
In the first screen, modify the code for push(). You can specify the type of value to be expected. Here we have a bool value.
bool value = await Navigator.push(context, MaterialPageRoute<bool>( builder: (context) => SecondScreen(myInt: 5) ) ); if (value) //checked after returning from next screen. //do something
And in the second screen modify the pop() function.
Navigator.pop(context, true);
This will return the boolean value of true back to the first screen.
Using named routes
The push statement can be made a lot smaller and simpler by using named routes. For this, we simply specify the list the screens of our app along with unique string names in our MaterialApp and then use this string name in the push statement. Optionally, we can also declare initialRoute with one of the routes in the list, instead of ‘home’ attribute.
MaterialApp( initialRoute: '/', routes: <String, WidgetBuilder>{ '/': (context) => FirstScreen(), '/pg2': (context) => SecondScreen(), '/pg3': (context) => ThirdScreen(), '/abt': (context) => AboutScreen(), }, )
Now, the code for navigating to the second screen will be simple like this:
Navigator.pushNamed(context, '/pg2');
Push a new screen as Replacement
To push a new screen and at the same time removing the current screen from the stack.
Navigator.pushReplacement(context, MaterialPageRoute<void>( builder: (BuildContext context) => NewScreen(), fullscreenDialog: true, ));
Or if you are using named routes, use pushReplacementNamed.
Navigator.pushReplacementNamed(context, '/pg2');
There’s another similar method called popAndPushNamed, but this one will execute the EXIT animation (animation of going back to a screen) in your app, instead of the ENTRY animation which happens with pushReplacementNamed mathod.
Navigator.popAndPushNamed(context, '/pg2');
To push a new screen and remove until a particular named screen.
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (BuildContext context) => AboutScreen()),
ModalRoute.withName('/pg1'),
);
The same functionality, when pushing a named route:
Navigator.pushNamedAndRemoveUntil(context, '/abt', ModalRoute.withName('/pg1') );
To push a new screen as the root of the app
That is, removing all the previous screens (including current screen) in the Navigator stack and pushing the new screen as root, we use pushAndRemoveUntil with the initial route name in flutter, that is ‘/’.
Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (BuildContext context) => NewScreen()), ModalRoute.withName('/') );
Passing data to a named route
Using onGenerateRoute
We have seen how using named routes makes the code reasonably simpler, but how to pass data when using them? This can be achieved using onGenerateRoute provided by MaterialApp.
So the first step is to modify our receiving screen widget to accept the argument that we want to send, in its constructor. This can be any data, a primitive data type or any custom data object also. Here we shall be sending a simple string value.
class AboutScreen extends StatelessWidget { final String argument; AboutScreen({this.argument}); }
Now we must define in our MaterialApp to send this data in the route builder where the screen object is constructed.
So go to your root MaterialApp widget and add it there:
MaterialApp( debugShowCheckedModeBanner: false, theme: buildLightTheme(), home: SplashScreen(), onGenerateRoute: (settings) { if (settings.name == '/abt') { String arg = settings.arguments; return MaterialPageRoute(builder: (_) => AboutScreen(argument: arg,)); } return null; //define for other screens }
)
Now we can push the screen with the data.
Navigator.pushNamed( context, '/abt', arguments: 'My argument string' );
A simpler method
If you don’t care about the data being present in a variable from initialization, then here’s a simpler way of paassing data, without having to use onGenerateRoute. This retrieves the data using ModalRoute with context, so it will be available only once you have the build context of the screen.
We simply add the argument to a Map while pushing.
Navigator.pushNamed(context, '/pg3', arguments: {'myData': 'Some String Data'});
If you want to pass a custom object data, you can do that as well. Suppose we have a custom object named MyObject.
Navigator.pushNamed(context, '/pg3', arguments: {'myData': MyObject(id: 1,title: 'Test String here')});
And to retrieve the data in our new screen:
@override Widget build(BuildContext context) { final Map arguments = ModalRoute.of(context).settings.arguments as Map; if (arguments != null) { //if string data print(arguments['myData']); //if you passed object final MyObject data = arguments['myData']; print(data.title); } return Scaffold( //the rest of your scaffold code here ); }
More…
If you’re looking for simplifying your code even more, take a look at the Get library.
Thank you for reading!
Leave A Comment