navigation_utils
Navigator 2 缺失的導航庫。NavigationUtils 是將 Flutter 的 Navigator 2 加入您應用程式的最簡單方法。
Flutter Navigator 2 缺失的導航庫。
{"sdk":"flutter"}{"sdk":"flutter"}^5.0.0以下為英文專案原文快照,最新內容請造訪 GitHub。
Pub release GitHub Release Date GitHub issues GitHub top language GitHub code size in bytes License
Screenshots
The missing navigation library for Flutter's Navigator 2.
NavigationUtils makes it easy to implement Navigator 2 in your Flutter app.
push(), pop(), pushAndReplace() and more.set().pushNamed(), setNamed().MaterialApp.router(
title: 'Navigation Utils Demo',
routerDelegate: NavigationManager.instance.routerDelegate,
routeInformationParser: NavigationManager.instance.routeInformationParser,
);
Tip: Navigator 2 utilizes MaterialApp.router and requires a RouterDelegate and RouteInformationParser. These components replace the routes and onGenerateRoute builders of Navigator 1.
The NavigationManager acts as a global singleton, serving as a dependency injector while holding references to the RouterDelegate and RouteInformationParser. See the customization section for more information on how to use your own dependency injection and custom navigation lifecycle management.
void main() {
NavigationManager.init(
mainRouterDelegate: DefaultRouterDelegate(navigationDataRoutes: routes),
routeInformationParser: DefaultRouteInformationParser());
runApp(const MyApp());
}
Tip: DefaultRouterDelegate and DefaultRouteInformationParser are convenience classes provided by this library to help you get up and running quickly. For more information on migrating an existing delegate or using a custom implementation, see the customization section.
List<NavigationData> get routes => [
NavigationData(
url: '/',
builder: (context, routeData, globalData) =>
const MyHomePage()),
NavigationData(
label: ProjectsPage.name,
url: '/projects',
builder: (context, routeData, globalData) =>
const ProjectsPage()),
];
Note: Each route requires a URL because NavigationData maps a URL to a specific page. The NavigationData model holds routing information that Flutter's navigator needs. For more insights on passing query parameters and using page constructors, see customization sections below.
NavigationData contains an optional label property to support named routing like in Navigator 1. Navigator 2 does not supported named routing out of the box so named routing is reimplemented. Here, ProjectsPage.name is a static constant defined in the ProjectPage widget.
class ProjectsPage extends StatefulWidget {
static const String name = 'projects';
@override
_ProjectsPageState createState() => _ProjectsPageState();
}
For new users, see the quick 5 line setup at example/lib/main.dart.
For a full, production ready configuration similar to the one used by 10,000+ users in Codelessly - A Flutter App and Website Builder, see example_auth/main.dart.
Features:
The NavigationData class in the NavigationUtils library is used to encapsulate all the necessary data for defining a route in your application. It provides an easy way to define and manage your routes.
NavigationData(
label: ProjectPage.name,
url: '/project',
builder: (context, routeData, globalData) =>
const ProjectPage(),
),
label: An optional String for named navigation.url: A String that represents the URL for the route. This is used to match the incoming route. It must start with a '/'.builder: A NavigationPageFactory object. It is a function that returns a Page widget. This builder is used to construct the page when the route is navigated to.pageType: An optional PageType enum that can be used to further customize the type of the page. The PageType can be material, cupertino, or transparent.fullScreenDialog: An optional bool that indicates whether the route is a full-screen modal dialog.barrierColor: An optional Color that specifies the color of the barrier that will appear behind the dialog. This is used only if fullScreenDialog is true.metadata: A Map<String, dynamic> that can hold any additional data you want to associate with the route.NavigationUtils supports path, name, and Route object-based routing. You can directly access these navigation functions through NavigationManager.instance.
Path-based routing can be considered "absolute" routing as each URL path is unique. The path is also the URL shown in the address bar on Web.
NavigationManager.instance.push('/projects');
Navigator 1's named route navigation. The name of the route is often defined in the respective page or component and used as a reference for navigation.
NavigationManager.instance.push(ProjectsPage.name);
Navigation can also use the raw Route object. Here, a DefaultRoute object is created with the specified path, which is then passed to the navigation. This method is primarily used internally and for supporting partial migrations to this library.
NavigationManager.instance.pushRoute(DefaultRoute(path: '/projects'));
Navigator 2 does not support query parameters, path parameters, route guards, or non-serializable objects out of the box. The default PageRoute class only supports URLs and arguments.
Please read and understand the following information as it is crucial to understanding how Navigator 2 works.
Important:
/home and /home/ are treated as two distinct pages.Navigator 2's default URL handling behavior is very limited and wrong by default for web. NavigationUtils adds support for URL routing parameters by extending PageRoute with a DefaultRoute and building an abstraction layer called NavigationData on top.
Access query parameters via routeData.queryParameters in NavigationData. Query parameters are stored in a Map<String, String> where the key is the query parameter name and the value is the query parameter value.
// Route Definition
NavigationData(
label: ProjectPage.name,
url: '/project',
builder: (context, routeData, globalData) => ProjectPage(
id: int.tryParse(routeData.queryParameters['id'] ?? ''),
),
)
// Route Navigation
NavigationManager.instance.push(ProjectPage.name);
NavigationManager.instance.push(ProjectPage.name, queryParameters: {'id': '320'});
NavigationManager.instance.push('/project');
NavigationManager.instance.push('/project', queryParameters: {'id': '320'});
ProjectPage is mapped to the URL ('/project'). An id query parameter is used to pass the ID of the project.
Note: All URL parameters are passed as Strings. This is because URLs are not "typed" and Strings by default.
ints and doubles with int.tryParse and double.tryParse.bools with routeData.queryParameters[variable] == 'true' where the value passed in the URL is a true or false String.Navigator 2 does not support query parameters "out of the box" as the Navigator 2 API does not have a query parameter field. By default, Navigator 2 treats query parameters as part of the URL string and different query parameters as unique pages.
For example, all of the below home / URLs are treated as different pages by Navigator 2:
/
/?tab=community_page
/?tab=community_page&post=80
/?tab=message_page
/?referrer=google_ads
This is a problem because all of the URLs should point to the same page and query parameters should be passed to that page. To support query parameters properly, this library strips query parameters from URLs, stores them, and then rebundles them during the route construction process.
Internally, this library extracts the query parameters (tab ) and stores it in the constructed DefaultRoute object, passing only the root / URL to the underlying Navigator 2 API.
Multiple NavigationData instances can be defined with different query parameters to handle various scenarios or variations of the same page, all pointing to the same destination.
Path parameters are used to capture dynamic parts of a URL's path. They are denoted by a colon (:) followed by a parameter name in the URL pattern. The corresponding values for each path parameter are extracted from the actual URL when a match is found.
Access path parameters via routeData.pathParameters in NavigationData. Path parameters are stored in a Map<String, String> where the key is the path parameter name and the value is the path parameter value.
// Route definition
NavigationData(
label: ProjectPage.name,
url: '/project/:projectId',
builder: (context, routeData, globalData) => ProjectPage(
id: int.tryParse(routeData.pathParameters['projectId'] ?? ''),
);
},
)
// Route navigation
NavigationManager.instance.push(ProjectPage.name);
NavigationManager.instance.push(ProjectPage.name, pathParameters: {'projectId': 320});
NavigationManager.instance.push('/project/320');
# Invalid: NavigationManager.instance.push('/project'); /project and /project/320 are different URLs.
In the example above, the ProjectPage is associated with the URL pattern '/project/:projectId'. The value of projectId is extracted from the actual URL. These parameters are then used to construct the ProjectPage with the corresponding values.
Multiple NavigationData instances can be defined with different URL patterns and path parameters to handle various routes and dynamic parts of the URL path.
Note: Ensure that the URL patterns in the NavigationData instances match the actual URLs accurately to enable correct parameter extraction.
/project and /project/:projectId are different URLs. To support both, define a NavigationData(url: '/project') and NavigationData(url: '/project/:projectId')./project/ does not pass a null ID to /project/:projectId. Instead, /project/ is equivalent to /project.Arbitrary data such as classes and non-serializable variables can be passed between pages with globalData. globalData can be used to pass anything between pages.
// Route Navigation
PostModel postModel = PostModel();
NavigationManager.instance.push(PostPage.name, globalData: {'postModel': postModel});
NavigationData(
label: PostPage.name,
url: '/post',
builder: (context, routeData, globalData) => ProjectPage(
postModel: globalData['postModel']),
);
},
)
A PostModel is passed to PostPage via globalData. The PostPage widget can now load the Post UI immediately.
globalDataBeyond its use during navigation, globalData can be accessed and modified at any point in your application at NavigationManager.instance.routerDelegate.globalData. This allows for setting data and configurations at anytime.
Example:
// Set or update data
NavigationManager.instance.routerDelegate.globalData['selected_variant'] = 'A';
// Access data
String variant = NavigationManager.instance.routerDelegate.globalData['selected_variant'];
Note: globalData is not bound to the page lifecycle so any variables set must be manually disposed. Any outdated variables will need to be explicitly cleared. Most of the time, opening a page will set and override the data so stale variables are not a concern.
Note: The URL of the page is used as the key for storing data.
A special feature of NavigationUtils is it supports deeplinks as data and defining them all in a single list. This is done by creating a list of DeeplinkDestination instances.
List<DeeplinkDestination> deeplinkDestinations = [
DeeplinkDestination(
deeplinkUrl: '/deeplink/login',
destinationLabel: LoginPage.name),
DeeplinkDestination(
deeplinkUrl: '/deeplink/signup',
destinationLabel: SignUpPage.name),
Each DeeplinkDestination represents a unique deeplink within your application and includes properties such as deeplinkUrl, destinationLabel, and destinationUrl to define the behavior of the deeplink.
This approach offers several advantages:
DeeplinkDestination(
deeplinkUrl: '/deeplink/login',
destinationLabel: LoginPage.name,
destinationUrl: '/login',
backstack: [InitializationPage.name, StartPage.name],
backstackRoutes: [InitializationRoute(), StartRoute()],
excludeDeeplinkNavigationPages: [ForgotPassword.name],
shouldNavigateDeeplinkFunction: () {
if (AuthService.instance.isAuthenticated) return false;
return true;
},
mapArgumentsFunction: (pathParameters, queryParameters) {
// Remap or process path and query parameters.
String referrerId = queryParameters['referrer'] ?? '';
InstallReferrer.instance.setReferrerId(referrerId);
return {'id': pathParameters['userId'] ?? ''};
},
runFunction: (pathParameters, queryParameters) async {
// Arbitrary function call for handling deeplinks without doing navigation.
},
authenticationRequired: false,
)
deeplinkUrl: A required property representing the deep link URL.destinationLabel: The named route destination of the deep link.destinationUrl: The URL route of the destination.backstack and backstackRoutes: Specify the route backstack to which the user should return when navigating away from the deep link. Only one of these can be set.excludeDeeplinkNavigationPages: A list of pages that should be excluded from deep link navigation.shouldNavigateDeeplinkFunction: A function that determines whether the deep link should be navigated.mapPathParameterFunction, mapQueryParameterFunction, mapArgumentsFunction, mapGlobalDataFunction: Optional functions that map path parameters, query parameters, arguments, and global data, respectively.runFunction: A function to support handling deeplinks without navigating, such as allowing deeplinks to trigger app functionality such as analytics, sharing data through social links, or showing a bottom sheet, without navigating to a new page. This function is also called after navigation completes, which allows for logic to be run after deeplink navigation is complete.authenticationRequired: A boolean indicating whether authentication is required to navigate the deeplink.By providing these parameters, NavigationUtils gives you the flexibility to customize deeplink behavior to suit your application's specific needs. Contributors are welcome to open an issue and PR to add additional functionality that might be missing.
NavigationUtils includes a convenience function called NavigationUtils.openDeeplinkDestination to process URIs and map them to deeplinks. Here is a sample implementation:
class DefaultRouteParser {
static bool openDeeplink(Uri? uri) {
return NavigationUtils.openDeeplinkDestination(
deeplinkDestinations: deeplinkDestinations,
routerDelegate: NavigationManager.instance.routerDelegate,
uri: uri,
authenticated: AuthService.instance.isAuthenticated,
currentRoute:
NavigationManager.instance.currentRoute,
excludeDeeplinkNavigationPages: doNotNavigateDeeplinkPages,
);
}
}
uri: The Uri object representing the deeplink that you want to open.
deeplinkDestinations: The list of DeeplinkDestination instances that define the deeplinks within your application.
routerDelegate: The BaseRouterDelegate instance that handles the actual navigation within your application.
deeplinkDestination: An optional DeeplinkDestination instance that you want to open. If not provided, the method will try to find the matching destination in the deeplinkDestinations list using the uri.
authenticated: A boolean value that indicates whether the user is authenticated. This is used when the DeeplinkDestination requires authentication. Defaults to true.
currentRoute: An optional DefaultRoute instance that represents the current route of the application. This is used for checking if the current page is in the excludeDeeplinkNavigationPages list.
excludeDeeplinkNavigationPages: A list of strings that represent the labels or paths of the routes that should be excluded from deeplink navigation. If the current route's label or path is in this list, the method will not perform the navigation.
redirectFunction: Redirect to another route.
This method tries to find the matching DeeplinkDestination for the given uri and performs various checks before navigating to the destination. These checks include checking whether the user is authenticated (if required), whether the current route is in the excluded list, and whether a custom navigation function allows the navigation. After these checks, the method navigates to the destination and processes any path parameters, query parameters, arguments, and global data as defined by the DeeplinkDestination. Finally, the method updates the route stack using the routerDelegate and applies the changes. The method returns true if the navigation was successful, and false otherwise.
Deeplinks can be redirected based on custom logic using the redirectFunction. The redirectFunction is used to handle deeplink redirections based on custom logic. It takes the current path and query parameters, applies the redirect logic, and determines whether to navigate to the original destination or to a different one.
Define the redirectFunction to specify the custom logic for redirections. The function is invoked with the current path and query parameters, along with a redirect callback to navigate to the new destination.
redirectFunction: (pathParameters, queryParameters, redirect) {
if (pathParameters.containsKey('id') && queryParameters.containsKey('action')) {
redirect(
label: 'newDestination',
pathParameters: {'id': pathParameters['id']!},
queryParameters: {'action': queryParameters['action']!},
globalData: {'additionalData': 'example'}
);
return Future.value(true);
}
return Future.value(false);
}
pathParameters: A Map<String, String> containing the current path parameters.queryParameters: A Map<String, String> containing the current query parameters.redirect: The callback function to navigate to the new destination.The redirectFunction returns a Future<bool>. If the function returns true, the redirection is considered successful, and the navigation proceeds to the new destination. If it returns false, the navigation proceeds to the original destination.
For single page apps, sometimes different URLs should map to the same Route. This fundamentally goes against Flutter's 1 to 1 URL to Route mapping. To solve this problem, the group parameter in the NavigationData class allows different URLs to map to the same page.
Define the group parameter in NavigationData to group multiple routes under the same destination.
NavigationData(
label: HomePage.name,
url: '/',
builder: (context, routeData, globalData) =>
HomePage(tab: routeData.queryParameters['tab'] ?? CommunityPage.name),
group: HomePage.name,
),
NavigationData(
label: CommunityPage.name,
url: '/community',
builder: (context, routeData, globalData) =>
HomePage(tab: CommunityPage.name),
group: HomePage.name,
),
NavigationData(
label: NewsPage.name,
url: '/news',
builder: (context, routeData, globalData) =>
HomePage(tab: NewsPage.name),
group: HomePage.name,
),
In this example, three different URLs ('/', '/community', and '/news') are mapped to the same HomePage. This feature is particularly useful when you want multiple routes to lead to the same page while maintaining state and animations.
Routes with the same group share the same widget instance, meaning the widget is reused rather than destroyed and recreated when navigating between grouped routes. This provides significant performance benefits:
didUpdateWidget is called instead of initStateNavigationData(
label: LoginForm.name,
url: '/login',
group: 'auth',
builder: (context, routeData, globalData) =>
AuthPage(type: AuthPageType.login),
metadata: {'type': 'auth'},
),
NavigationData(
label: SignUpForm.name,
url: '/signup',
group: 'auth',
builder: (context, routeData, globalData) =>
AuthPage(type: AuthPageType.signup),
metadata: {'type': 'auth'},
),
Important: When grouping routes that reuse widgets:
Do NOT use const constructors - The const keyword prevents Flutter from detecting widget parameter changes. Use regular constructors to ensure didUpdateWidget is called properly.
// ❌ Wrong - const prevents widget updates
builder: (context, routeData, globalData) =>
const AuthPage(type: AuthPageType.login),
// ✅ Correct - allows widget to detect parameter changes
builder: (context, routeData, globalData) =>
AuthPage(type: AuthPageType.login),
Implement didUpdateWidget - Override this method in your StatefulWidget to handle parameter changes:
@override
void didUpdateWidget(AuthPage oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.type != widget.type) {
setState(() {
// Update state when type changes
});
}
}
This pattern is ideal for:
Flutter's Page Update Detection:
When Navigator 2 determines whether to update or recreate a Page's child widget, it uses Page.canUpdate:
didUpdateWidgetinitStateFor grouped routes:
canUpdate returns true (same key), the Route is reusedbuildPage method reads _page.child at build time_page (settings) now points to the new Page with a new child widget, the new child is returneddidUpdateWidgetThe Critical Mechanism:
The Route must read the child from settings (the Page) at build time, not capture it at creation time. This ensures that when navigating between grouped routes, the Route always uses the latest child widget from the Page settings, triggering didUpdateWidget as expected.
For detailed technical documentation, see docs/FLUTTER_NAVIGATOR2_PAGE_UPDATE_MECHANISM.md.
NavigationUtils supports the common "Authenticated" route guard through the authenticationRequired boolean.
DeeplinkDestination with authenticationRequired.NavigationUtils.openDeeplinkDestination(authenticated: AuthService.instance.isAuthenticated).When the user is on certain pages, such as the onboarding page, you may often want to disable deeplinks. NavigationUtils supports this behavior with excludeDeeplinkNavigationPages.
excludeDeeplinkNavigationPages. This list accepts named routes and path routes.currentRoute like currentRoute: NavigationManager.instance.currentRoute.Setup Custom Route Guards by tagging NavigationData routes with custom metadata.
First, add custom tags to NavigationData(label: PremiumMemberPage.name, url: '/premium_page', metadata: {kUserStatus: PREMIUM}). Here, the PremiumPage is tagged with kUserStatus and requires a PREMIUM status to navigate.
if (NavigationManager.instance.routerDelegate
.currentConfiguration?.metadata?['kUserStatus'] == PREMIUM) {
DefaultRouteParser.openDeeplink(uri);
}
NavigationUtils supports route guards through the shouldNavigateDeeplinkFunction property of the DeeplinkDestination class. This function is called before navigating to the deeplink destination and can be used to prevent navigation based on certain conditions. For example, you can check if a user is authenticated before allowing navigation to a protected route.
NavigationUtils supports asynchronous navigation, allowing you to perform asynchronous tasks such as data fetching or authentication checks before navigating to a deeplink destination. This is facilitated by the fact that the shouldNavigateDeeplinkFunction can be an asynchronous function, meaning it can return a Future<bool> instead of a simple bool. This lets you perform any necessary async operations and delay navigation until those operations complete.
NavigationUtils allows you to customize route transitions both globally and on a per-page basis.
You can define a global transition that will be applied to all routes unless overridden by a local (per-page) transition. To set a global transition, create a custom Page class with a custom Route that reads the child at build time.
⚠️ Important: Do NOT use
PageRouteBuilderfor custom transitions. It captures the child widget in a closure at Route creation time, which prevents page updates when using grouped routes or query parameter changes. Always create a custom Route class that reads_page.childat build time.
// Define a Custom Page for your global transition
class ScaleTransitionPage extends Page<void> {
final Widget child;
const ScaleTransitionPage({
required this.child,
super.key,
super.name,
super.arguments,
});
@override
Route<void> createRoute(BuildContext context) {
return _ScaleTransitionRoute(page: this);
}
}
// Custom Route that reads child at BUILD TIME (not creation time)
class _ScaleTransitionRoute extends PageRoute<void> {
_ScaleTransitionRoute({required ScaleTransitionPage page})
: super(settings: page);
// Read from settings at build time - this is the key!
ScaleTransitionPage get _page => settings as ScaleTransitionPage;
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
bool get maintainState => true;
@override
Duration get transitionDuration => const Duration(milliseconds: 300);
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return _page.child; // Read child from CURRENT page at build time
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return ScaleTransition(
scale: animation,
alignment: Alignment.center,
child: child,
);
}
}
// Override pageBuilder
NavigationManager.init(
mainRouterDelegate: DefaultRouterDelegate(
navigationDataRoutes: routes,
pageBuilder: ({
key,
name,
child,
routeData,
globalData,
arguments,
}) =>
ScaleTransitionPage(
key: key,
name: name,
arguments: arguments,
child: child,
),
),
routeInformationParser: DefaultRouteInformationParser(),
);
This will override the default MaterialPage transition animation and apply a scale transition to all pages. The custom Route class ensures that didUpdateWidget() is called correctly when the page updates.
To override the global transition for a specific route, create a custom Page class with its own Route and use the pageBuilder property in NavigationData:
// Define a custom Page for your per-page transition
class RightToLeftTransitionPage extends Page<void> {
final Widget child;
const RightToLeftTransitionPage({
required this.child,
super.key,
super.name,
super.arguments,
});
@override
Route<void> createRoute(BuildContext context) {
return _RightToLeftRoute(page: this);
}
}
// Custom Route that reads child at BUILD TIME
class _RightToLeftRoute extends PageRoute<void> {
_RightToLeftRoute({required RightToLeftTransitionPage page})
: super(settings: page);
RightToLeftTransitionPage get _page => settings as RightToLeftTransitionPage;
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
bool get maintainState => true;
@override
Duration get transitionDuration => const Duration(milliseconds: 300);
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return _page.child; // Read child at build time
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
}
// Usage in NavigationData:
NavigationData(
label: 'Details',
url: '/details',
builder: (context, routeData, globalData) => DetailsPage(),
pageBuilder: ({key, name, child, routeData, globalData, arguments}) {
return RightToLeftTransitionPage(
key: key,
name: name,
arguments: arguments,
child: child,
);
},
),
In this example, the DetailsPage will have a right-to-left slide transition, overriding any global transition.
class NoTransitionPage extends Page<void> {
final Widget child;
const NoTransitionPage({
required this.child,
super.key,
super.name,
super.arguments,
});
@override
Route<void> createRoute(BuildContext context) {
return _NoTransitionRoute(page: this);
}
}
class _NoTransitionRoute extends PageRoute<void> {
_NoTransitionRoute({required NoTransitionPage page}) : super(settings: page);
NoTransitionPage get _page => settings as NoTransitionPage;
@override
bool get opaque => true;
@override
bool get barrierDismissible => false;
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
bool get maintainState => true;
@override
Duration get transitionDuration => Duration.zero;
@override
Duration get reverseTransitionDuration => Duration.zero;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return _page.child; // Read from CURRENT page at build time!
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return child; // No transition animation
}
}
// In your main app initialization:
NavigationManager.init(
mainRouterDelegate: DefaultRouterDelegate(
navigationDataRoutes: routes,
pageBuilder: ({
key,
name,
child,
routeData,
globalData,
arguments,
}) => NoTransitionPage(
key: key,
name: name,
arguments: arguments,
child: child,
),
),
routeInformationParser: DefaultRouteInformationParser(),
);
To disable transitions for a specific page, use the NoTransitionPage within the pageBuilder of the corresponding NavigationData:
NavigationData(
label: 'No Animation',
url: '/no-animation',
builder: (context, routeData, globalData) => NoAnimationPage(),
pageBuilder: ({key, name, child, routeData, globalData, arguments}) {
return NoTransitionPage(
key: key,
name: name,
arguments: arguments,
child: child,
);
},
),
You can create any custom transition effect you need by defining your own Page classes and using them either globally or on a per-page basis.
NavigationUtils implements intelligent page caching to optimize performance and provide smooth navigation experiences.
🎯 Unique Feature: This caching system is exclusive to NavigationUtils. Flutter's Navigator 2 and popular libraries like go_router have a bug which causes significant performance issues when there are 5+ pages in the navigation stack as all pages get rebuilt on every navigation event. This works by default in Navigator 1, but requires special care due to opaque Navigator internal equality checks. NavigationUtils brings back this essential optimization by intelligently caching and reusing page instances.
The caching system automatically handles:
For a comprehensive guide on how cache keys are generated and managed, see CACHE_BEHAVIOR.md.
Query Parameters Update Pages:
push('/product?id=1'); // Creates page
push('/product?id=2'); // Updates same page with new data
Grouped Routes Share Instances:
NavigationData(url: '/', group: 'home'),
NavigationData(url: '/games', group: 'home'),
// Both share the same widget instance
Duplicates Create New Instances:
push('/item'); // First instance
push('/item'); // Second instance (separate page)
When developing, you may add or change routes and expect hot reload to pick up the updates. Unfortunately, hot reload does not work out of the box with Flutter's Navigator widget if it is static, which it needs to be to avoid recreating itself on every navigation event. To enable hot reload, add the following to your top level App widget.
@override
void reassemble() {
NavigationManager.instance.routerDelegate.navigationDataRoutes = routes;
super.reassemble();
}
Why this works: In Flutter, hot reload only reloads the widget build path.
routes, as top-level variables are not re-initialized on hot reload.final references so new instances can be created on hot reload.Navigator internal variables.