auto_route 0.6.6 auto_route: ^0.6.6 copied to clipboard
AutoRoute is a declarative routing solution, where everything needed for navigation is automatically generated for you.
auto_route: #
AutoRoute is a declarative routing solution, where everything needed for navigation is automatically generated for you.
- Installation
- Setup and Usage
- Customizing routes
- Passing arguments to routes
- Dynamic routing aka path parameters
- Extracting route parameters
- Handling unknown routes
- Nested routes
- Navigation
- Route guards
- Handling wrapped routes
- Custom route transitions
- Migration guide
Installation #
dependencies:
auto_route: [latest-version]
dev_dependencies:
auto_route_generator: [latest-version]
build_runner:
Setup and Usage #
First create a router class and annotate it with @MaterialAutoRouter, @CupertinoAutoRouter, @AdaptiveAutoRouter or @CustomAutoRoute. It's name must be prefixed with $ to get a generated class with the same name minus the $. $Router => Router
Note: using $ prefix is mandatory.
@MaterialAutoRouter(...config) //CustomAutoRoute(..config)
class $Router {}
Declare your AutoRoutes in MaterialAutoRouter() annotation
@MaterialAutoRouter(
routes: <AutoRoute>[
// initial route is named "/"
MaterialRoute(page: HomeScreen, initial: true),
MaterialRoute(page: UsersScreen, ..config),
],
)
class $Router {}
Now simply run the generator
Use the [watch] flag to watch the files' system for edits and rebuild as necessary.
flutter packages pub run build_runner watch
if you want the generator to run one time and exits use
flutter packages pub run build_runner build
Finalize the setup
after you run the generator your router class will be generated Let MaterialApp use ExtendedNavigator instead of the native one by assigning it to its builder.
MaterialApp(
...
// builder uses the native nav key to keep
// the state of ExtendedNavigator so it won't reload
// when using Flutter tools-> select widget mode
builder: ExtendedNavigator.builder<Router>(router: Router(),
// pass anything navigation related to ExtendedNav instead of MaterialApp
initialRoute: ...
observers:...
navigatorKey:...
onUnknownRoute:...
),
// use the passed builder within ExtendedNavigator
// instead of MaterialApp builder to wrap your navigator
// with other widgets
MaterialApp(
builder: ExtendedNavigator.builder(
router: Router(),
builder: (context, extendedNav) => Theme(
data: ThemeData(brightness: Brightness.dark),
child: extendedNav,
),
),
Using the native navigator #
Note Without ExtendedNavigator you will lose support for RouteGuards and auto-nested navigation handling.
MaterialApp(
// assign your generated Router directly to onGenerateRoute property
onGenerateRoute: Router()
);
Inside of the generated file
// a Routes class that holds all of your static route names
class Routes {
static const String homeScreen = '/';
static const String usersScreen = '/users-screen';
static const all = <String>{
homeScreen,
usersScreen,
};
}
class Router extends RouterBase {
@override
List<RouteDef> get routes => _routes;
final _routes = <RouteDef>[
RouteDef(Routes.homeScreen, page: HomeScreen),
RouteDef(Routes.usersScreen, page: UsersScreen),
];
@override
Map<Type, AutoRouteFactory> get pagesMap => _pagesMap;
final _pagesMap = <Type, AutoRouteFactory>{
HomeScreen: (data) {
return MaterialPageRoute<dynamic>(
builder: (context) => HomeScreen(),
settings: data,
);
},
...
// Argument holder classes if exist ...
Customizing routes #
There are several available auto-route types which you can customize
- MaterialRoute -> will generate a MaterialRoutePage
- CupertinoRoute -> will generate a CupertinoRoutePage
- AdaptiveRoute -> will generate Cupertino or Material route page based on the Platform
- CustomRoute -> will generate a PageRouteBuilder with the provided Customizations
@MaterialAutoRouter(
routes: <AutoRoute>[
MaterialRoute(page: HomeScreen, initial: true, name: "InitialRoute"),
CupertinoRoute(page: UsersScreen, fullscreenDialog: true),
//This route returns result of type [bool]
CustomRoute<bool>(page: LoginScreen, transitionsBuilder: TransitionsBuilders.fadeIn),
],
)
class $Router {}
Custom path names
AutoRoute automatically generates paths based on page type, for example the generated path for HomeScreen is "/home-screen". You probably won't need to customize your paths unless your are building a web application. To use a custom path name use the path property inside of AutoRoute.
MaterialRoute(path: "/users", page: UsersScreen)
Available customizations
MaterialAutoRouter | CupertinoAutoRouter | AdaptiveAutoRouter
Property | Default value | Definition |
---|---|---|
generateNavigationHelperExtension [bool] | false | if true a Navigator extension will be generated with helper push methods of all routes |
routePrefix [String] | '' | all route paths will be prefixed with this routePrefix String |
routesClassName [string] | 'Routes' | the name of the generated Routes class |
CustomAutoRouter
Property | Default value | Definition |
---|---|---|
transitionsBuilder | null | extension for the transitionsBuilder property in PageRouteBuilder |
opaque | true | extension for the opaque property in PageRouteBuilder |
barrierDismissible | false | extension for the barrierDismissible property in PageRouteBuilder |
durationInMilliseconds | null | extension for the transitionDuration(millieSeconds) property in PageRouteBuilder |
MaterialRoute | CupertinoRoute | AdaptiveRoute | CustomRoute
Property | Default value | Definition |
---|---|---|
initial | false | mark the route as initial '\' |
path | null | an auto generated path will be used if not provided |
name | null | this will be assigned to the route variable name if provided and it will be used to name the route's nested Router if it has one (String homeScreen = [name]); |
fullscreenDialog | false | extension for the fullscreenDialog property in PageRoute |
maintainState | true | extension for the maintainState property in PageRoute |
CupertinoRoute Specific => CupertinoPageRoute
Property | Default value | Definition |
---|---|---|
title | null | extension for the title property in CupertinoPageRoute |
CustomRoute Specific => PageRouteBuilder
Property | Default value | Definition |
---|---|---|
transitionsBuilder | null | extension for the transitionsBuilder property in PageRouteBuilder |
opaque | true | extension for the opaque property in PageRouteBuilder |
barrierDismissible | false | extension for the barrierDismissible property in PageRouteBuilder |
durationInMilliseconds | null | extension for the transitionDuration(millieSeconds) property in PageRouteBuilder |
Passing arguments to routes #
That's the fun part!
You don't actually need to do anything extra. AutoRoute automatically detects your route parameters and handles them for you, it will automatically generate a class that holds your screen arguments and keep them typed.
class WelcomeScreen extends StatelessWidget {
final String title;
final String message;
const WelcomeScreen({this.title = "Default Title",@required this.message});
@override
Widget build(BuildContext context)...
}
Generated arguments holder for the above example
- Default values are respected.
- Required fields are also respected and handled properly.
class WelcomeScreenArguments {
final String title;
final String message;
// you're not going to lose your default values;
WelcomeScreenArguments({this.title = "Default Title",@required this.message});
}
Pass your typed args using the generated arguments holder class
ExtendedNavigator.of(ctx).pushNamed(Router.welcomeScreenRoute,
arguments: WelcomeScreenArguments(
title: "Hello World!"
message: "Let's AutoRoute!"
)
);
Dynamic routing aka path parameters #
requires AutoRoute: >= 0.6.0 Define a dynamic segment by prefixing it with a colon
MaterialRoute(path: "/users/:id", page: UsersScreen);
Now pushing users/1
will match /users:id
and the path parameter id
will be extracted and exposed in
RouteData
Tip: add a question mark after a dynamic segment to make it optional
MaterialRoute(path: "/users/:id?", page: UsersScreen);
Now pushing /users
or /users/1
will navigate you to the UsersScreen
Extracting route parameters #
Extracted path parameters & queryParameters are bundled inside of an object called RouteData which you can get by calling RouteData.of(context)
inside of the current route page.
class UsersScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
var routeData = RouteData.of(context)
// .value will return the raw string value
var userId = routeData.pathParams['id'].value;
// .intValue will return a parsed int or null if parsing fails
int userId = routeData.pathParams['id'].intValue;
var queryParams = routeData.queryParams;
...
isn't there a better way to extract route parameters? This doesn't feel very auto! #
of course there is! simply annotate your constructor parameters with @PathParam()
or @QueryParam()
and let auto_route do the work for you
class UsersScreen extends StatefulWidget {
const UsersScreen({
@PathParam() this.id,
// if your var name is different from the param name you
// can pass it's expected name to the annotation
// e.g [/users/1?filter=testers]
@QueryParam('filter') this.filterFromQuery,
});
Generated code for UsersScreen
MaterialPageRoute<dynamic>(
builder: (context,) => UsersScreen(
// auto_route will know the right type to parse the raw value to
id: data.pathParams['id'].intValue,
filterFromQuery: data.queryParams['filter'].stringValue)
Note constructor parameters annotated with @PathParam()
or @QueryParam
will not be considered as argument parameters and will be excluded from the generated argument class holder
Handling unknown routes #
requires AutoRoute: >= 0.6.0
Using a wildcard
AutoRoute 0.6.0 supports wildcard matching, you can simply declare a wildcard at the end of your routes list to catch any undefined routes.
@MaterialAutoRouter(
routes: <AutoRoute>[
MaterialRoute(page: HomeScreen, initial: true),
MaterialRoute(page: UsersScreen),
// This should be at the end of your routes list
// wildcards are represented by '*'
MaterialRoute(path: "*", page: UnknownRouteScreen)
],
)
class $Router {}
Using the onUnknownRoute callback function
This function is called when the matcher fails to find a route to return, a default error page is returned if onUnknownRoute is not provided
ExtendedNavigator(
router: Router(),
onUnknownRoute:(RouteSettings settings){
// return your Error page
} ,
...
If you're using auto_route with the native Navigator
use the function inside of MaterialApp
MaterialApp(
onGenerateRoute: Router(),
onUnknownRoute:(RouteSettings settings){
// return your Error page
} ,
...
Nested Routes #
Declaring your nested routes inside of the parent route's children property will generate a nested router class called UsersScreenRouter, if you provide a custom name for the parent route the nested router name will be customName+Router
@MaterialAutoRouter(
routes: <AutoRoute>[
MaterialRoute(page: HomeScreen, initial: true),
MaterialRoute(
path: '/users:id',
page: UsersScreen,
children: <AutoRoute>[
// path: '/' is the same as setting initial to true
MaterialRoute(path: '/', page: ProfileScreen),
MaterialRoute(path: '/posts', page: PostsScreen),
],
),
],
)
class $Router {}
Now we need to render these nested routes inside of their parent UsersScreen and for that we use an ExtendedNavigator()
, without providing a Router as it's automatically provided by the parent route.
this is the same as using <router-outlet>
in Angular or <router-view>
in Vue.
class UsersScreen extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Users Page")),
// this navigator will obtain it's router
// on it's own
body: ExtendedNavigator(),
);
}
}
Now simply navigate to the nested route using the regular push method
ExtendedNavigator.of(context).push('/users/1/posts')
Navigation #
You can either use context to look up your Navigator in your widgets tree or without context, by name.
with context
ExtendedNavigator.of(context).push(...)
// or
context.navigator.push(...)
context.rootNavigator.push(...)
by Name -> requires AutoRoute: >= 0.6.0 Note: if you don't provide a name the navigator will be named after it's router class's type name
// give your navigator a name
ExtendedNavigator(router: Router(), name: "nestedNav")
//call it by it's name
ExtendedNavigator.named("nestedNav").push(...)
if you're working with only one navigator
ExtendedNavigator.root.pushNamed(..)
Navigation helper methods extension #
to generate extension methods set the generateNavigationHelperExtension property inside of MaterialAutoRouter() to true
This will generate
extension RouterNavigationHelperMethods on ExtendedNavigatorState {
Future pushHomeScreen() => pushNamed(Routes.homeScreen);
Future<bool> pushSecondScreen(
{@required String title, String message}) =>
pushNamed<bool>(Routes.secondScreen,
arguments: SecondScreenArguments(title: title, message: message));
}
Then use it like follows
ExtendedNavigator.of(context).pushSecondScreen(args...);
//or
ExtendedNavigator.named('nestedNav').pushSecondScreen(args...)
Route guards #
Implementing route guards requires a little of setup:
- Create your route guard by extending RouteGuard from the autoRoute package
class AuthGuard extends RouteGuard {
@override
Future<bool> canNavigate(
ExtendedNavigatorState navigator, String routeName, Object arguments) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString('token_key') != null;
}
}
- Register the guards inside of ExtendedNavigator
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: ExtendedNavigator<Router>(
router: Router(),
guards: [AuthGuard()],
),
);
}
}
Finally assign the guards to the route you want to protect
MaterialRoute(page: ProfileScreen, guards: [AuthGuard])
Handling Wrapped Routes #
To wrap your route with a parent widget like a Provider or such, simply implement AutoRouteWrapper, and let wrappedRoute(context) method return (this) as the child of your wrapper widget.
class ProductsScreen extends StatelessWidget implements AutoRouteWrapper {
@override
Widget wrappedRoute(BuildContext context) {
return Provider(create: (ctx) => ProductsBloc(), child: this);
...
Custom Route Transitions #
To use custom Transitions use the @CustomRoute() annotation and pass in your preferences. The TransitionsBuilder function needs to be passed as a static/const reference that has the same signature as the TransitionsBuilder Function of the PageRouteBuilder class. The included TransitionsBuilders Class contains a preset of common Transitions builders
@CustomRoute(transitionsBuilder: TransitionBuilders.slideBottom,durationInMilliseconds: 400)
LoginScreen loginScreenRoute;
Use @CustomAutoRouter() to define global custom route Transitions.
You can of course use your own transitionsBuilder function as long as it has the same function signature. The function has to take in exactly a BuildContext, Animation[Double], Animation[Double] and a child Widget and it needs to return a Widget, typically you would wrap your child with one of flutter's transition Widgets as follows.
Widget zoomInTransition(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
// you get an animation object and a widget
// make your own transition
return ScaleTransition(scale: animation, child: child);
}
Now pass the reference of your function to CustomRoute() .
CustomRoute(page: ZoomInScreen, transitionsBuilder: zoomInTransition)
Migration guide #
I apologize for the good 5 to 10 minutes you're gonna lose rewriting your router class but it's for the greater good ;).
Migrating from auto_route <= 0.5.0
Basically instead of declaring our routes as class fields we're going to use a more readable and scalable way (a static routes list).
old way <= 0.5.0
@MaterialAutoRouter()
class $Router{
@initial
HomeScreen homeScreen;
}
new way >= 0.6.0
@MaterialAutoRouter(
routes:[
MaterialRoute(page: HomeScreen, initial: true),
]
)
class $Router{}
Route Customization
old way <= 0.5.0
@MaterialAutoRouter()
class $Router{
@CupertinoRoute(fullscreenDialog: true, returnType: bool)
LoginScreen loginScreen;
@CustomRoute(transitionBuilder: TransitionsBuilders.fadeIn)
ProfileScreen profileScreen;
}
new way >= 0.6.0
@MaterialAutoRouter(
routes:[
CupertinoRoute<bool>(page: LoginScreen, fullscreenDialog: true),
CustomRoute(page: ProfileScreen, transitionBuilder: TransitionsBuilders.fadeIn)
],
)
class $Router{}
Route Guards
old way <= 0.5.0
@MaterialAutoRouter()
class $Router{
@GuardedBy[AuthGuard]
ProfileScreen profileScreen;
}
new way >= 0.6.0
@MaterialAutoRouter(
routes:[
MaterialRoute(page: ProfileScreen, guards:[AuthGuard]),
],
)
class $Router{}
Custom UnknownRoute screen
old way <= 0.5.0
@MaterialAutoRouter()
class $Router{
@unknownRoute
UnknownRouteScreen unknownRoute;
}
new way >= 0.6.0
@MaterialAutoRouter(
routes:[
...
// order is important here, this must be at the end of your routes list
MaterialRoute(path: '*', page: UnknownRouteScreen),
],
)
class $Router{}
Problems with the generation? #
Make sure you always Save your files before running the generator, if that doesn't work you can always try to clean and rebuild.
flutter packages pub run build_runner clean
Support auto_route #
You can support auto_route by liking it on Pub and staring it on Github, sharing ideas on how we can enhance a certain functionality or by reporting any problems you encounter