I'm writing a web flutter application with go_router
.
Below is a code sample to illustrate how it is architectured (you can also check out this repo).
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
final router = GoRouter(
routes: [
GoRoute(
path: '/',
redirect: (_) => '/persons',
),
GoRoute(
path: '/persons',
builder: (_, __) => const PersonsScreen(),
routes: [
GoRoute(
path: ':id',
pageBuilder: (_, __) => const DrawerPage(
child: PersonScreen(),
),
),
],
),
],
);
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
routeInformationProvider: router.routeInformationProvider,
);
}
}
class PersonsScreen extends StatelessWidget {
const PersonsScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Persons'),
ElevatedButton(
onPressed: () => GoRouter.of(context).push('/persons/1'),
child: const Text('Push'),
)
],
),
),
);
}
}
class PersonScreen extends StatelessWidget {
const PersonScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.deepPurple,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Person'),
ElevatedButton(
onPressed: GoRouter.of(context).pop,
child: const Text('Pop'),
)
],
),
),
);
}
}
class DrawerPage extends Page {
const DrawerPage({
required this.child,
});
final Widget child;
@override
Route createRoute(BuildContext context) {
return _DrawerPageRoute(
settings: this,
);
}
}
class _DrawerPageRoute extends TransitionRoute {
_DrawerPageRoute({
required DrawerPage settings,
}) : super(
settings: settings,
);
@override
Iterable<OverlayEntry> createOverlayEntries() {
return [
OverlayEntry(
builder: (context) {
return Row(
children: [
const Spacer(flex: 2),
Expanded(
child: SlideTransition(
position: animation!.drive(
Tween(
begin: const Offset(1, 0),
end: const Offset(0, 0),
),
),
child: (settings as DrawerPage).child,
)),
],
);
},
),
];
}
@override
bool get opaque => false;
@override
Duration get transitionDuration => const Duration(milliseconds: 200);
}
It has 2 pages:
/persons
/persons/:id
/persons
is just a grey screen:
When I click on "Push", it pushed /persons/1
which is a transparent route that takes a 3 of the screen:
This works well and I can still see the previous screen related to /persons
behind the DrawerPage
.
And when I click on "Pop", it does remove the purple screen (as it should).
But what I don't understand is when I paste myself the URL in the browser's address bar (for example /persons/3
), it also ends up with the purple page that takes 1/3 of the screen (this is fine), but for some reason, the grey screen (linked to /persons
) is behind and I can pop to go to the previous page (/persons
) when I click on "Pop".
How and why is this page built?
How does flutter/GoRouter decide that when going to /persons/:id
it should insert persons/
as the first page?
This is how it works.
According to the documentation (https://gorouter.dev/sub-routes):
go_router will match the routes all the way down the tree of sub-routes to build up a stack of pages
The route /persons/id
includes the screen /persons
in the stack and that is why it is shown.
If you want a screen showing /persons/id
without the stack, you could define a different route where /persons/id
is the root. Like:
final router = GoRouter(
routes: [
GoRoute(
path: '/',
redirect: (_) => '/persons',
),
GoRoute(
path: '/persons',
builder: (_, __) => const PersonsScreen(),
routes: [
GoRoute(
path: ':id',
pageBuilder: (_, __) => const DrawerPage(
child: PersonScreen(),
),
),
],
),
GoRoute(
path: '/person/:id',
pageBuilder: (_, __) => const DrawerPage(
child: PersonScreen(),
),
),
],
);