Flutter’s navigation and routing definitions are great tools for handling moving from one page of an application to another and back. In particular named routes make it simple to decouple implementation from intent. The standard navigation behaviour using named routes is to create a MaterialPageRoute
using a widget. If you want to use a popup route or some other modal artifact that doesn’t work. Turns out it’s simple to override this behaviour and this post explains how.
Basic named routes
Using navigation with named routes is pretty simple. First declare the route in the routes table as part of the MaterialApp
definition. Then use it with a navigator, those two pieces look like this:
MaterialApp(
title: "Routing Demonstration",
routes: {
'/': (context) => RoutingHomePage(),
MySecondScreen.routeName: (context) => MySecondScreen(),
}
),
....
// In some other method use the named route
Navigator.pushNamed( context, MySecondScreen.routeName );
The limitation to this mechanism is that it automatically creates a MaterialPageRoute
to wrap the widget that is provided as the entry to the routes
table. In the example above the two widgets are RoutingHomePage
and MySecondScreen
. When the pushNamed
method is called on the navigator a new MaterialPageRoute
that refers to one of those widgets is created. That works fine to completely replace the current page, but not for popups.
Handling custom routes
There is a mechanism that allows particular named routes to be handled differently while still using the routes table for all of the common cases. The trick is that there is an onGenerateRoute
property on the MaterialApp
class that takes a function that dynamically generates routes for a request. That function is only invoked if the requested route isn’t defined in the routes
table. The documentation for the onGenerateRoute
property explains this.
Given that behaviour here’s how to handle a call to pushNamed
by creating a popup or other route.
MaterialApp(
title: "Routing Demonstration",
onGenerateRoute: _generateRoute
routes: {
'/': (context) => RoutingHomePage(),
MySecondScreen.routeName: (context) => MySecondScreen(),
}
),
...
Route _generateRoute( RouteSettings settings ) {
switch (settings.name) {
case MyPopupScreen.routeName:
return MyPopupRoute();
break;
}
return null;
}
...
// In some other method use the named route
Navigator.pushNamed( context, MyPopupRoute.routeName );
Note that the routes
table hasn’t changed. What’s new is the onGenerateRoute
property and the _generateRoute
function. Now _generateRoute
will be called whenever a named route is requested that isn’t present in the routes
table. Since MyPopupRoute.routeName
isn’t in that table, when that route is requested in a pushNamed
call, the _generateRoute
function is invoked. Note that _generateRoute
returns a Route
, not a Widget
. The MyPopupRoute
class referenced above extends PopupRoute
to satisfy that signature. It would look something like this:
class MyPopupRoute extends PopupRoute {
static const String routeName = "/mypopup";
Widget buildPage ( BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation ) {
return Placeholder();
}
}