go_router v3.0 comes with two breaking changes:
- signature of the
navigatorBuilder
function - behavior and signature of the
GoRouter.pop
function
In previous version, the navigatorBuilder
function was called with a
BuildContext
and a Widget?
. You could implement it like so:
final _router = GoRouter(
routes: ...,
// add a wrapper around the navigator to put loginInfo into the widget tree
navigatorBuilder: (context, child) => ChangeNotifierProvider<LoginInfo>.value(
value: loginInfo,
child: child,
builder: (context, child) => child!,
),
);
Two problems with this signature were that the child
parameter was nullable,
even though it would never be null
. This was done to reuse the
TransitionBuilder
typedef, but the result was that a navigatorBuilder
function implementation would need to check for null
; annoying!
Even worse, if you wanted to drive the navigatorBuilder
function
implementation with information like the navigation location, it wasn't
available as a parameter; super annoying!
In v3, both of these problems were addressed by changing the signature of
navigatorBuilder
:
/// The signature of the navigatorBuilder callback.
typedef GoRouterNavigatorBuilder = Widget Function(
BuildContext context,
GoRouterState state,
Widget child,
);
This allows an implementation to use the location, e.g. like this from the nested navigation samples:
navigatorBuilder: (context, state, child) => Material(
child: Column(
children: [
Expanded(child: child),
Padding(
padding: const EdgeInsets.all(8),
child: Text(state.location), // now you can use GoRouterState
),
],
),
),
This implementation shows the current location as the user navigates through the app, something that was harder to do in previous versions.
In previous versions, the GoRouter.pop
function simply called Navigator.pop
.
This was somewhat non-intuitive, since, unlike the rest of the GoRouter
methods, it required a BuildContext
parameter:
// global GoRouter
final _router = GoRouter(...);
...
TextButton(
onPressed: () async {
if (await abandonNewPerson(context)) {
_router.pop(context); // pass the context, please
}
},
child: const Text('Cancel'),
),
Some developers prefer to make their GoRouter
instance global so that they can
call them without the use of a context, which meant they couldn't call pop
.
This was done as a simple way to implement GoRouter.pop
by simply forwarding
to Navigator.pop
, which itself was non-intuitive; go_router has it's own stack
of pages, calling pop
should just work with those pages, right?
Either way, you can continue to call Naviator.pop(context)
if that's the
behavior you want. Otherwise, you can use the simpler version of GoRouter.pop
:
// global GoRouter
final _router = GoRouter(...);
...
TextButton(
onPressed: () async {
if (await abandonNewPerson(context)) {
_router.pop(); // no context for you!
}
},
child: const Text('Cancel'),
),
If you do have context and are using the Dart extension, your code won't need to change at all:
TextButton(
onPressed: () async {
if (await abandonNewPerson(context)) {
context.pop(); // no code changes needed in this case
}
},
child: const Text('Cancel'),
),