In the example, I have an /about route which is triggered by clicking on the question icon in the AppBar. I want the AboutScreen to be pushed over the nested routes, so I am declaring it as a peer route alongside the nested routes.
Visually, this is working for me as regardless of where you are in the routing, clicking on the AppBar about icon navigates to the AboutScreen, however the pop() behavior is broken which I believe is a result of how I made the AboutRoute function like a stacked route, even though it is a top-level route.
VWidget(
path: about,
widget: Container(),
stackedRoutes: [
VPopHandler(
onPop: (vRedirector) async {
// This approach works, but will it return to the previous url
// consistently?
final url = vRedirector.previousVRouterData?.previousUrl;
if (url != null) {
final queryParams =
vRedirector.previousVRouterData!.queryParameters;
vRedirector.to(url, queryParameters: queryParams);
}
},
stackedRoutes: [
VWidget(path: null, widget: AboutScreen()),
],
)
],
)
Full example
```dart
import 'package:flutter/material.dart';
import 'package:vrouter/vrouter.dart';
main() {
runApp(
VRouter(
initialUrl: ProfileRoute.profile,
routes: [
ShopRoute(),
ProfileRoute(),
AboutRoute(),
],
debugShowCheckedModeBanner: false,
),
);
}
/// This is a top-level route defined outside of any nested route because I want
/// it to display on top of all UX (fill the viewport). In order for it to
/// properly set the `canPop` check, I have to place the VRoute into a
/// `stackedRoutes` property. Note: I cannot simply place it into
/// `VPopHandler.stackedRoutes`, but instead I have to nest it into a dummy
/// `VWidget` with a `Container` as its widget that is never seen.
class AboutRoute extends VRouteElementBuilder {
static final String about = '/about';
@override
List buildRoutes() {
return [
VWidget(path: about, widget: Container(), stackedRoutes: [
VPopHandler(
onPop: (vRedirector) async {
// TODO: Why doesn't the standard onPop handler deal with this
// case? Performing a `context.vRouter.pop()` navigates to
// '/about' (same as current location). I feel this problem is
// related to the nesting of the AboutScreen inside a dummy VWidget
//
// This approach works, but will it return to the previous url
// consistently?
final url = vRedirector.previousVRouterData?.previousUrl;
if (url != null) {
final queryParams =
vRedirector.previousVRouterData!.queryParameters;
vRedirector.to(
url,
queryParameters: queryParams
);
}
},
stackedRoutes: [
VWidget(path: null, widget: AboutScreen()),
],
)
]),
];
}
}
class ShopRoute extends VRouteElementBuilder {
static final int index = 0;
static final String shop = '/shop';
static final String order = shop + '/order';
@override
List buildRoutes() {
return [
ScaffoldRouteElement(
path: shop,
index: index,
nestedRoutes: [
VWidget(
path: null,
// TODO: I assume this alias has to exist because the VNester has
// to be in the path even for it's stackedRoutes to be discovered?
// Would it be better practice to use a wildcard to cover all paths
// which are subroutes of this VNester?
aliases: [shop + '/*'],
widget: ShopScreen(),
key: ValueKey('Shop'),
),
],
stackedRoutes: [VWidget(path: order, widget: OrderScreen())],
)
];
}
}
class ProfileRoute extends VRouteElementBuilder {
static final int index = 1;
static final String profile = '/profile';
static final String settings = profile + '/settings';
@override
List buildRoutes() {
return [
ScaffoldRouteElement(
path: profile,
index: index,
nestedRoutes: [
VWidget(
path: null,
aliases: [settings],
widget: ProfileScreen(),
// TODO: Source comments indicate assigning this key will prevent
// animation of the ProfileScreen when navigating from
// ProfileScreen to SettingsScreen. Is that the purpose in this
// case? (I didn't notice a difference in behavior whether the key
// is specified or not. I assume the default value is achieving
// the same behavior?)
key: ValueKey('Profile'),
stackedRoutes: [
VWidget(path: settings, widget: SettingsScreen()),
],
),
],
)
];
}
}
class ScaffoldRouteElement extends VRouteElementBuilder {
static final navigatorKey = GlobalKey();
final String path;
final int index;
final List nestedRoutes;
final List? stackedRoutes;
ScaffoldRouteElement({
required this.path,
required this.index,
required this.nestedRoutes,
this.stackedRoutes,
});
@override
List buildRoutes() {
return [
VNester(
// According to docs, the same key and navigatorKey ensure that the two
// instances of this VNester will retain the same RenderObject; I am not
// noticing any difference with the next two lines commented out or not,
// but I am not doing anything stateful in my example
key: ValueKey('MyScaffold'),
navigatorKey: navigatorKey,
path: path,
widgetBuilder: (child) => MyScaffold(child: child, index: index),
nestedRoutes: nestedRoutes,
stackedRoutes: stackedRoutes ?? [],
)
];
}
}
class MyScaffold extends StatelessWidget {
final Widget child;
final int index;
const MyScaffold({required this.child, required this.index});
@override
Widget build(BuildContext context) {
final title = index == ProfileRoute.index
? "Profile Scaffold"
: index == ShopRoute.index
? 'Shop Scaffold'
: 'Unknown Scaffold';
return Scaffold(
backgroundColor: Colors.white,
// TODO: Why is the AppBar's back button not visible when canPop is true?
appBar: AppBar(
title: Text(title),
actions: [
Padding(
padding: const EdgeInsets.only(right: 24),
child: IconButton(
// TODO: how do I make canPop return true when this route loads?
onPressed: () => context.vRouter.to(AboutRoute.about),
icon: Icon(Icons.question_mark),
),
),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: index,
items: [
BottomNavigationBarItem(icon: Icon(Icons.shop), label: 'Shop'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
onTap: (int index) {
if (index == ProfileRoute.index) {
context.vRouter.to(ProfileRoute.profile);
} else if (index == ShopRoute.index) {
context.vRouter.to(ShopRoute.shop);
} else {
throw Exception('Unknown navigation target');
}
},
),
body: child,
);
}
}
abstract class BaseWidget extends StatelessWidget {
String get title;
String get buttonText;
String get to;
@override
Widget build(BuildContext context) {
final canPop = ModalRoute.of(context)?.canPop;
return Material(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('$title Screen'),
SizedBox(height: 50),
ElevatedButton(
onPressed: () => context.vRouter.to(to),
child: Text(buttonText),
),
SizedBox(height: 50),
Text('canPop? $canPop'),
if (canPop == true)
ElevatedButton(
onPressed: () {
return context.vRouter.pop();
},
child: Text('Pop'),
),
],
),
),
);
}
}
class ShopScreen extends BaseWidget {
@override
String get title => 'Shop';
@override
String get buttonText => 'Go to order (stacked outside nested path)';
@override
String get to => ShopRoute.order;
}
class OrderScreen extends BaseWidget {
@override
String get title => 'Order';
@override
String get buttonText => 'Go to Settings (stacked in a different path)';
@override
String get to => ProfileRoute.settings;
}
class SettingsScreen extends BaseWidget {
@override
String get title => 'Settings';
@override
String get buttonText => 'Go to Order';
@override
String get to => ShopRoute.order;
}
class ProfileScreen extends BaseWidget {
@override
String get title => 'Profile';
@override
String get buttonText => 'Go to Settings (stacked in nested path)';
@override
String get to => ProfileRoute.settings;
}
class AboutScreen extends BaseWidget {
@override
String get title => 'About';
@override
String get buttonText => 'Go to Profile';
@override
String get to => ProfileRoute.profile;
}
```
In the example, I have an
/about
route which is triggered by clicking on the question icon in theAppBar
. I want theAboutScreen
to be pushed over the nested routes, so I am declaring it as a peer route alongside the nested routes.Visually, this is working for me as regardless of where you are in the routing, clicking on the
AppBar
about icon navigates to theAboutScreen
, however thepop()
behavior is broken which I believe is a result of how I made theAboutRoute
function like a stacked route, even though it is a top-level route.Full example
```dart import 'package:flutter/material.dart'; import 'package:vrouter/vrouter.dart'; main() { runApp( VRouter( initialUrl: ProfileRoute.profile, routes: [ ShopRoute(), ProfileRoute(), AboutRoute(), ], debugShowCheckedModeBanner: false, ), ); } /// This is a top-level route defined outside of any nested route because I want /// it to display on top of all UX (fill the viewport). In order for it to /// properly set the `canPop` check, I have to place the VRoute into a /// `stackedRoutes` property. Note: I cannot simply place it into /// `VPopHandler.stackedRoutes`, but instead I have to nest it into a dummy /// `VWidget` with a `Container` as its widget that is never seen. class AboutRoute extends VRouteElementBuilder { static final String about = '/about'; @override List