Manage App Navigation
Routing is implemented using the most up to date Router and Page API from the Flutter SDK. This allows us to control the entire navigation stack and how it behaves. It also allows us to support URL synchronization and deep linking. Unlike using Navigator.push
, this navigation solution is compatible for all platforms and allows for complex routing solutions.
This router is configured to work without context
, using a RouterService
.
Default Routes
The boilerplate comes with two routes which cannot be removed, or else navigation will break.
/
- The home page./404
- The 404 page, when a route is not found.
final routes = [ RouteEntry(path: '/', builder: (key, routeData) => const HomeView()), RouteEntry(path: '/404', builder: (key, routeData) => const NotFoundView()),];
Adding a New Route
To add a new route, you can use the RouteEntry
constructor.
RouteEntry(path: '/new-route', builder: (key, routeData) => const NewRouteView()),
But you can also add dynamic routes. These must be formatted using :
in front of the dynamic parameter within the path.
RouteEntry(path: '/new-route/:id', builder: (key, routeData) => const NewRouteView(id: routeData.pathParameters['id'])),
Using a Route
The RouterService
is available app wide, and there are 6 different ways to navigate.
goTo
To navigate to a new route, you can use the goTo
method.
locator<RouterService>().goTo(Path(name: '/new-route'));
replace
To replace the current route, you can use the replace
method.
locator<RouterService>().replace(Path(name: '/new-route'));
back
To navigate back, you can use the back
method.
locator<RouterService>().back();
backUntil
To navigate back until a specific route, you can use the backUntil
method.
locator<RouterService>().backUntil(Path(name: '/new-route'));
replaceAll
To replace all routes, you can use the replaceAll
method.
locator<RouterService>().replaceAll([Path(name: '/new-route'), Path(name: '/new-route-2')]);
remove
To remove a specific route, you can use the remove
method.
locator<RouterService>().remove(Path(name: '/new-route'));
Passing Data to a Route
There are 3 different ways to pass data to a route.
Path Parameters
We metioned the first approach using path parameters in the Adding a New Route section.
// configure the route in route_config.dartRouteEntry(path: '/todos/:id', builder: (key, routeData) { final id = routeData.pathParameters['id']; return TodoView(id: id);}),
// go to the routelocator<RouterService>().goTo(Path(name: '/todos/123'));
Query Parameters
You can also pass data using queryParameter
, which is a property when pushing to pass a custom object.
// configure the route in route_config.dartRouteEntry(path: '/todos', builder: (key, routeData) { final id = routeData.queryParameters['id']; return TodoView(id: id);}),
// go to the routelocator<RouterService>().goTo(Path(name: '/todos?id=123'));
Extra
The first 2 methods are utilizing the URL, but you can also pass the data directly to the route using extra
, which allows you to pass a custom object.
// configure the route in route_config.dartRouteEntry(path: '/todos', builder: (key, routeData) { final todo = routeData.extra as Todo; return TodoView(id: todo.id, name: todo.name);}),
// go to the routelocator<RouterService>().goTo(Path(name: '/todos', extra: Todo(id: '123', name: 'Test')));
Automatic Routing Depending on Auth
A common use case is to automatically route to a different page depending on the authentication state. To achieve this, you would listen to the authenticated state and navigate from there. Here is a simple example.
class AuthService { AuthService({required RouterService routerService, required FirebaseAuth firebaseAuth}) : _routerService = routerService, _firebaseAuth = firebaseAuth { // synchronous check of routing checkAuthState(firebaseAuth.currentUser?.uid);
// listen to user state for routing firebaseAuth.authStateChanges().listen((User? user) { checkAuthState(user?.uid); }); }
void checkAuthState(String? uid) { if (uid == null) { routerService.replaceAll([ Path(name: '/login'), ]); } else { routerService.replaceAll([ Path(name: '/'), ]); } }}
Note
Because the stream takes time to emit its first value, we need to set the route synchronously, otherwise you will have a transition to ”/” and then a transition to “/login”.