Last updated:
0 purchases
owlet router
Owlet Router #
The owlet_router is a route manager. It utilizes the route builder to
construct the router.
It is designed with several purposes in mind:
Providing a clear and easily definable route manager that is simple to read and use.
Built upon the base Flutter Router, allowing for integration with various page route types and the
ability to customize and extend the router.
Enabling modularization of the router, making it easy to segment routes and create independent
routes.
Offering the capability to check, prevent, or redirect routes before they are pushed.
Table of content #
Getting started
Usage
Features
1. NavigationService
1.1. Owlet Navigator
2. Provider
3. Route history
4. Unknown Route
5. Route
5.1. Route builder
5.2. Route usage
5.3. Custom RouteBuilder
5.4. Troubleshoot
6. Nested Navigator
Inspiration and References
Features and bugs
Getting started #
Add this line to your dependencies:
dependencies:
rowlet_router: '0.0.2'
copied to clipboard
import the package:
import 'package:rowlet_router/router.dart';
copied to clipboard
Usage #
Create your AppRoute class for the top-level route (the root route). This route has 2 direct
children splash and home.
class AppRoute extends RouteBase {
class AppRoute():super.root(); // The default to set the root segments is '/'
final splash = MaterialRouteBuilder(
'/', pageBuilder: (context, settings) => const SplashPage());
final home = MaterialRouteBuilder(
'/home', pageBuilder: (context, settings) => const HomePage());
final profiles = ProfileRoute('/profile');
List<RouteBase> get children => [splash, home, profiles];
}
copied to clipboard
The ProfileRoute is a child route module of AppRoute. It has 2 children, the details
page (/profile/) and the update profile page (/profile/update-profile).
class ProfileRoute extends RouteBase {
class ProfileRoute(super.segment);
final details = MaterialRouteBuilder(
'/', pageBuilder: (context, settings) => const DetailsPage());
final update = MaterialRouteBuilder(
'/update-profile', pageBuilder: (context, settings) => const UpdateProfilePage());
List<RouteBase> get children => [details, update];
}
copied to clipboard
To utilize this route, you must inject the top-level route (AppRoute) into your navigator using
the NavigationService.
final appRoute = AppRoute(); // The root route
final service = NavigationService(
navigationKey: GlobalKey(),
routeObservers: [ /* your observers */],
initialRoute: '/',
root: appRoute,
unknownRoute: owletDefaultUnknownRoute);
// ...
// Use in root navigator
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: service.routerConfig,
/// ...
);
}
// *************************************************
// Use in nested navigator
Widget build(BuildContext context) {
return OwletNavigator(service);
}
copied to clipboard
To push the profile route, you can use the normal push method of Navigator:
final result = await Navigator.of(context).pushNamed('/profile/');
copied to clipboard
... or call from the route field.
final result = await appRoute.profiles.pushNamed(
args: //...,
params: // ... query parameters
fragment: // #route-fragment,
);
copied to clipboard
Features #
1. NavigationService #
The NavigationService provides the necessary method for a new Navigator:
Navigator(
key: service.navigationKey,
initialRoute: service.initialRoute,
observers: <NavigatorObserver>[service.history, ...service.routeObservers],
onGenerateRoute: service.onGenerateRoute,
onPopPage: service.onPopPage,
onUnknownRoute: service.onUnknownRoute,
);
copied to clipboard
Owlet Navigator #
The OwletNavigator is a custom implementation of Navigator designed to offer advanced features
like route guards and named functions. By default, the RouterDelegate utilizes
the OwletNavigator.
Widget build(BuildContext context) {
return OwletNavigator(service);
}
copied to clipboard
2. Provider #
To optimize performance, consider making the NavigationService a singleton within the Navigator.
This can be achieved by creating a singleton variable or utilizing dependency injection, such as
GetIt.
The NavigationServiceProvider allows you to access the NavigationService within the context.
final service = NavigationService.of(context);
copied to clipboard
Additionally, creating routes as static fields can enhance performance. However, keep in mind that
static routes cannot dynamically change their parent routes. It's not necessary to create every
route as a static field, the module manager features can still operate with individual routes,
allowing you to create representative routes for each part of the application.
To locate the corresponding RouteBase object within the current context, traverse the route tree.
final route = RouteBase.of<ROUTE_TYPE>(context);
copied to clipboard
However, this approach may result in a worst-case time complexity of O(n * k), where 'n' represents
the average depth of the route tree and 'k' represents the number of NavigationService layers. As
mentioned earlier, an alternative approach is to determine the route type directly from the part
route.
final route = profiles.findType<ROUTE_TYPE>();
copied to clipboard
3. Route history #
The NavigationService offers a route history observer that logs the current routes.
void listenHistory() {
final history = service.history;
history.addListener(() {
/// Listen when routes change.
if (kDebugMode) {
print(history.current);
}
});
final currentRoute = history.current; // Get the current route which is displayed on the top.
bool isAppeared = history.contains('/home'); // check if the /home route is showing on display
// find a route that matches the condition, if you need to do something with it such as the Navigator.replacement.
final route = history.nearest(/* condition */);
}
copied to clipboard
4. Unknown route #
If your route cannot be found or if an error occurs during its construction, the unknown route will
be used as a replacement.
final service = NavigationService(
// ...
unknownRoute: yourRoute
);
copied to clipboard
5. Route #
The owlet_router utilizes a module architecture for routing. To define a route, create a class
that extends RouteBase. Inside this class, you can specify the children of the route. Repeat this
process for child routes to expand the route tree
final appRoute = AppRoute();
// ..
class AppRoute extends RouteBase {
MainRoute() : super.root(); // if this route is the root route
final splash = MaterialRouteBuilder(
'/', pageBuilder: (context, settings) => const SplashPage());
final home = MaterialRouteBuilder(
'/home', pageBuilder: (context, settings) => const HomePage());
final items = ListItemRoute('/item');
@override
List<RouteBase> get children => [splash, home, items];
}
class ListItemRoute extends RouteBase {
ListItemRoute(super.segment);
final list = MaterialRouteBuilder(
'/list', pageBuilder: (context, settings) => const ListItemPage());
late final detail = RouteGuard(
routeBuilder: RouteBuilder<String, dynamic>(
'/detail',
builder: (settings) {
if (settings.arguments is String) {
return MaterialPageRoute(
settings: settings,
builder: (context) =>
DetailPage(item: settings.arguments as String),
);
}
return CancelledRoute(false);
},
),
routeGuard: (context, route) async {
if (!navigatorServices.history.contains(list.path)) {
Navigator.push(context, list.noAnimationBuilder()!);
}
return route;
},
);
@override
List<RouteBase> get children => [detail, list];
}
copied to clipboard
Warning
Don't forget to add your route to the List<RouteBase> get children getter. If it's not
registered there, the route will not be found.
5.1. Route builder #
Think of RouteBase as a folder; it doesn't create anything, making it a non-launchable route. To
create a page route, we use RouteBuilder, which is a launchable route.
final page1 = RouteBuilder(
'/page1',
builder: (settings) =>
MaterialPageRoute(
settings: settings,
builder: (context) => Page1(arguments: settings.arguments),
),
);
copied to clipboard
The RouteBuilder allows you to customize your PageRoute, and we have created
some custom RouteBuilder. You can also customize your PageRoute by
overriding the RouteBuilder.builder method.
5.2. How does it work? #
What is the route path?
To obtain the path of the splash, you can use the splash.path method. The splash path is
generated by concatenating its parent path with its own path. If the result contains duplicate
slashes (//), they will be merged into one. Therefore, if you wish for the segment to have the
same route as its parent, simply use a single slash (/) as its segment.
Push a new route
If you know the path, a simplified approach is to use Navigator.pushNamed to push it onto the
Navigator.
Navigator.of(context).pushNamed('/home');
// or
Navigator.of(context).pushNamed(appRoute.home.path);
copied to clipboard
The RouteBuilder's extension provides an other method to push it
appRoute.home.pushNamed(context);
copied to clipboard
Argument and result
The RouteBuilder<A, T>.pushNamed method provides information about the route's argument type
and result type
final page2 = RouteBuilder<String, bool>(
'/page2',
builder: (settings) {
final greeting = settings.arguments as String?;
return MaterialPageRoute(
builder: (context) => TestApp(content: greeting),
);
},
);
// ...
final bool result = await page2.pushName(context, args: /* String type is required*/);
copied to clipboard
In a module:
If ListItemRoute is located within a module package, you cannot access the appRoute because
it is
defined in the main package. Instead, you can use RouteBase.of<ListItemRoute>() to specifically
retrieve your items route. This approach promotes greater independence of your code between
different packages.
5.3. Custom RouteBuilder #
MaterialRouteBuilder: It will create a MaterialPageRoute.
CupertinoRouteBuilder: It will create a CupertinoPageRoute.
NoTransitionRouteBuilder: It will create a PageRoute without any transition when appears.
final splash = MaterialRouteBuilder('/', pageBuilder: (context, settings) => const SplashPage());
copied to clipboard
RouteGuard:
It exclusively operates with the OwletNavigator. The RouteGuard offers
a routeGuard method that is invoked before pushing the route. This method allows you to
modify the route before pushing it.
To prevent the route from being pushed, return a CancelledRoute. To redirect it, return
another route or use a RedirectRoute('redirect-path') within the routeGuard method."
final detail = RouteGuard(
route: RouteBuilder<String, dynamic>(
'/detail',
builder: (settings) {
if (settings.arguments is String) {
return MaterialPageRoute(
settings: settings,
builder: (context) => DetailPage(item: settings.arguments as String));
}
return CancelledRoute(false);
},
),
routeGuard: (context, route) async {
if (!navigatorServices.history.contains(list.path)) {
Navigator.push(context, list.noAnimationBuilder()!);
}
return route;
},
);
copied to clipboard
Note
The RouteGuard works only with these functions:
Navigator.push,
Navigator.pushNamed,
Navigator.popAndPushNamed,
Navigator.pushReplacement,
Navigator.pushReplacementNamed,
Navigator.pushAndRemoveUntil,
Navigator.pushNamedAndRemoveUntil
NamedFunctionRoute:
An ideal approach is to name a function and call it using the Navigator. When pushing a
NamedFunctionRoute, no route is added; instead, the defined function is invoked, and its result
will be returned.
Similar to the RouteGuard, it also functions exclusively with the OwletNavigator.
final action = NamedFunctionRouteBuilder(
'/action',
callback: (context, route) => print('Hello World'),
);
copied to clipboard
Note
The NamedFunctionRouteBuilder works only with these functions:
Navigator.pushNamed,
Navigator.popAndPushNamed,
5.4. Troubleshoot #
When using the appRoute.profiles.update.path method to obtain a route, and you expect the path
to be /home/profiles/update, but the result is /update, ensure that you have included this
route within the children getter of the profile route.
When updating the route's children and encountering Troubleshoot#1, even if
it
already exists in the children list, make sure you have called the repair method after making
the route changes.
For optimal performance, the router should be more stable and undergo fewer changes. Therefore,
if you encounter issues when changing the route and performing a hot reload, consider utilizing
the 'hot restart' method to resolve this issue. While the repair method can be effective, it
demands additional resources, so it is advisable to avoid using it in release mode.
6. Nested Navigator #
To use a nested navigator. Create a NestedPage with the OwletNavigator:
static final nestedService = NavigatorSerivce( /*...*/ );
class NestedPage extends StatefullWidget {
/// ...
}
class NestedPageState extends State<NestedPage> {
/// ...
Widget build(BuildContext context) {
return OwletNavigator(nestedService);
}
}
copied to clipboard
In the root route, create a field with NestedService:
class AppRoute extends RouteBase {
// ...
final nestedPage = NestedService(
service: nestedService,
route: MaterialRouteBuilder('/nested', pageBuilder: (context, settings) => const NestedPage());
);
}
copied to clipboard
Whenever you push a route as /nested/sub-path/**, if the NestedPage already exists in the
Navigator, the nestedService will be pushed with the path /sub-path/**. Otherwise, a new
NestedPage will be pushed onto the Navigator.
Navigator.of(context, rootNavigator: true).pushNamed('/nested/sub-path/**');
copied to clipboard
NestedRoute #
Similar to a nested navigator, nested routes facilitate the division of a route into two components.
The actual route gets injected into the navigation service, while the remaining segment serves as an
argument for the page widget.
class ItemDetailPageState extends State<ItemDetailPage> {
late final NestedRoute nestedRoute;
@override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
nestedRoute = RouteBase.of<NestedRoute>(context);
nestedRoute.addListener(onRouteNotifier);
});
}
@override
void dispose() {
nestedRoute.removeListener(onRouteNotifier);
super.dispose();
}
void onRouteNotifier() {
print(nestedRoute.value as RouteSettings?);
}
}
//....
class AppRoute extends RouteBase {
// ...
final itemDetail = NestedRoute(
route: MaterialRouteBuilder('/item-detail', pageBuilder: (context, settings) => const ItemDetailPage());
);
}
copied to clipboard
Inspiration and References #
This package draws inspiration from the flutter_modular
package and incorporates concepts from the following resources:
Navigator,
Provider.
navigation_history_observer,
Additional resources from various forums have also been consulted during the development of this
package.
Features and bugs #
Please file feature requests and bugs at
the issue tracker.
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.