I would like to use Flutter’s (2.0) navigation for routing on my mobile app. I cannot find cookbook examples and followed the recommended guide, implementing the nested router example.
NOTE ===
If a rendered view has a unique route (uri) within the app I call it a Page.
If it does not have a unique route, I call it a Screen.
========
The base router selects between pages in the app. The nested router in the “Resource Dashboard” uses the resourceViewState object to select the screen to render within the Resource Dashboard” Page. Just by using the selectedIndex as below, I can change the screen depending on which index the user has selected in a Material Design Drawer.
As a result, when the user is on any non-default screen (i.e. Details A, Details B) in the above diagram, and there is a pop event, the user is returned to the default screen. If the user pops from the default screen, they are returned to the “Select Resource” Page outside of the Nested Router. But I have one more sticky case to handle (or maybe I am not handling these cases well :))
@override
Widget build(BuildContext context) {
int selectedIndex = resourceViewState.selectedIndex;
return Navigator(
key: navigatorKey,
pages: [
MaterialPage(
key: ValueKey(DEFAULT_PAGE),
child: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(‘DEFAULT’),
),
drawer: ResourceDrawer(
resourceViewState: resourceViewState,
navKey: navigatorKey,
),
body: DefaultPage(),
),
),
if (selectedIndex != DEFAULT_PAGE) ...[
MaterialPage(
key: ValueKey(selectedIndex),
child: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(_getTitle(selectedIndex)),
),
drawer: ResourceDrawer(
deviceState: deviceState,
navKey: navigatorKey,
),
body: _getScreen(selectedIndex),
),
),
],
],
onPopPage: (route, result) {
if (result == "return") {
print("return ");
return route.didPop(true);
}
resourceViewState.selectedIndex = DEFAULT_PAGE;
notifyListeners();
return route.didPop(false);
},
);
}
I want to navigate the user from an Alert Dialog to the “Select Resource” page when a button is clicked in the dialog. To do so I must (see 1, 2, 3 in diagram).
3 seems to be handled by the base router if the user pops from the default screen, but I also need to do so from an Alert Dialog.
This has the effect shown by the red arrow in the diagram.
I can simply use Navigator.of(context).pop() for the first 2. I am pretty sure that the Navigator used here is different from that used by the Nested Router (would love some details here). I believe this is the case, because onPopPage is not called for the NestedRouter on these events.
For 3) I have tried this strategy:
a) Call navKey.currentState!.pop(“disconnected”) from the Navigation Drawer. I pass in the navKey of the Nested Router as shown in the code above.
b) Now the onPopPage listener registered with the nested router receives the result from this pop event.
onPopPage: (route, result) {
if (result == "return") {
print("return ");
return route.didPop(true);
}
resourceViewState.selectedIndex = DEFAULT_PAGE;
notifyListeners();
return route.didPop(false);
},
When I see result == "return" then I should navigate from the “Resource Dashboard” Page to the “Select Resource” Page. But I am not sure how to do it nor if it is even a good strategy to use different views on the same route (tangent).
This is a working solution. I pass a reference to the parent navigator key, then call pop from it. I would prefer for it to be entirely declarative but I am not sure how to implement with the nested navigator pattern.
class ResourcePage extends StatefulWidget {
ResourcePage({
required this.resourceViewState,
required this.navigatorKey,
});
final ResourceViewState resourceViewState;
final GlobalKey<NavigatorState> navigatorKey;
@override
_ResourcePageState createState() => _ResourcePageState();
}
class _ResourcePageState extends State<DevicePage> {
late DeviceDelegate _routerDelegate;
late ChildBackButtonDispatcher _backButtonDispatcher;
@override
void didChangeDependencies() {
super.didChangeDependencies();
// Defer back button dispatching to the child router
_routerDelegate = InnerRouterDelegate(parentNavigatorKey: widget.navigatorKey, resourceViewState: widget.resourceViewState);
_backButtonDispatcher = Router.of(context).backButtonDispatcher!.createChildBackButtonDispatcher();
_backButtonDispatcher.takePriority();
}
@override
void didUpdateWidget(covariant DevicePage oldWidget) {
super.didUpdateWidget(oldWidget);
_routerDelegate.state = widget.resourceViewState;
}
@override
Widget build(BuildContext context) {
return Router(
routerDelegate: _routerDelegate,
backButtonDispatcher: _backButtonDispatcher,
);
}
}
// InnerRouterDelegate Snippets
// Constructor
InnerRouterDelegate({
required this.resourceViewState,
required this.parentNavigatorKey,
}) {
resourceViewState.addListener(notifyListeners); // See Nested Navigation Example
}
// On Pop Page
onPopPage: (route, result) {
if (result == "return") {
parentNavigatorKey.currentState!.pop();
return route.didPop(true);
}
resourceViewState.selectedIndex = DEFAULT_PAGE;
notifyListeners();
return route.didPop(false);
},