Closed rydmike closed 2 years ago
@lulupointu you were looking at Overlays and nested navigation weren't you?
Yes I did, I fixed the issue by manually inserting an Overlay
on top of navigationBuilder
, fixing this type of issues.
I believe you have this same issue when you try and use MaterialApp.builder
to wrap a scaffold in Nav 1, which has always been a pain point. Nice fix!!
You are right, I have seen that mentioned somewhere too. Feels like the Overlay fix should be included in the SDKs MaterialApp.builder too.
Yes I did, I fixed the issue by manually inserting an
Overlay
on top ofnavigationBuilder
, fixing this type of issues.
@lulupointu What does the code look like to manually insert the Overlay
? Is that something we should just do?
@lulupointu What does the code look like to manually insert the Overlay? Is that something we should just do?
Yes, you can have a look at the TabbedRoutes
repo its in go_router.dart
and its documented.
Definitely would be appreciated if the router makes sure there is always an overlay on top, this would be needed for dialogs, drop-down menus or bottom sheets that are shown within the scaffold as well,
@lulupointu What does the code look like to manually insert the Overlay? Is that something we should just do?
Yes, you can have a look at the
TabbedRoutes
repo its ingo_router.dart
and its documented.
@lulupointu I do not see any use of an Overlay
widget in your https://github.com/lulupointu/go_router fork. What does the Overlay
code look like?
If I just take the most basic go_router example and add a navigatorBuilder
with the child wrapped with an Overlay
, navigation still happens but the widget tree is not re-rendered:
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() => runApp(App());
class App extends StatelessWidget {
App({Key? key}) : super(key: key);
static const title = 'GoRouter Example: Declarative Routes';
@override
Widget build(BuildContext context) => MaterialApp.router(
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate,
title: title,
);
final _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const Page1Screen(),
),
GoRoute(
path: '/page2',
builder: (context, state) => const Page2Screen(),
),
],
// navigatorBuilder w/ Overlay
navigatorBuilder: (context, state, child) => Overlay(
initialEntries: [
OverlayEntry(
builder: (context) => child,
)
],
),
);
}
class Page1Screen extends StatelessWidget {
const Page1Screen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => context.go('/page2'),
child: const Text('Go to page 2'),
),
],
),
),
);
}
class Page2Screen extends StatelessWidget {
const Page2Screen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('Go to home page'),
),
],
),
),
);
}
https://user-images.githubusercontent.com/2568253/148506322-f2629617-4af6-46ff-bf7b-60cd7364c888.mov
@lulupointu I do not see any use of an Overlay widget in your https://github.com/lulupointu/go_router fork. What does the Overlay code look like?
I think it's mainly useful when building inside navigatorBuilder
. For example if you build a scaffold with a bottom navigation bar inside navigatorBuilder
, the bottom navigation bar will need an overlay. This is also shown in the first comment of this thread which put a Tooltip
in navigatorBuilder
.
If I just take the most basic go_router example and add a navigatorBuilder with the child wrapped with an Overlay, navigation still happens but the widget tree is not re-rendered
Yes, that's because Overlay
is not declarative, you need to rebuild it manually. Basically you build it once and then you call _overlayEntry!.markNeedsBuild()
whenever needed. Here is where I do it in my go_router implementation.
@esDotDev @rydmike @lulupointu I have a branch that wraps an Overlay
around the Navigator
but it still doesn't quite solve all of the problems, e.g. there's no Navigator
to use to show a dialog from the widgets created by the navigatorBuilder
, which makes sense but is irritating.
On the other hand, it's easy enough to wrap the widget return by navigatorBuilder
inside a single page Navigator
(shown in the shared scaffold sample) and seems to solve all problems, including requiring no need to change the implementation of GR. Thoughts?
Right, nesting a Navigator with a single home page above the router is one way to get everything to work properly, but always felt pretty cludgy to me.
Usually I will just work around this by assigning a global key to the MaterialApp.navigatorKey
, and pass that into my appManager/appBloc in order to show dialogs, pages and bottom sheets etc.
The issue with missing an Overlay though is particularly a pain, because many material widgets use ToolTips, so you pretty much always need it, and not having it just leads to various material widgets just blowing up. It's not as easy to workaround as the Navigator stuff.
So, tl;dr, kinda breaks down like:
Though, to be fair, if you want full-screen dialogs and sheets that cover your scaffolding, there really is no choice but to use a 2nd Navigator, so I dunno... It's complicated!
The use of a root Navigator fixes my Overlay problems. I assume because the former provides one of the latter.
re: a 2nd navigator -- that's something a GR user can do.
re: a 2nd overlay -- that's only really something I can do for them, but I agree that it's a lighter weight solution to the problem.
If I could figure out how to call showDialog using your global navigator key trick, then that would be worth exploring in combo w/ 2nd overlay.
Should be able to just do something like:
showDialog(_navKey.currentContext!);
but again, that approach won't cover anything outside of the Navigator
bounds, so in cases where you want full-screen dialogs to cover some scaffolding, you are basically forced to add a wrapping Navigator
(which yes, does solve the Overlay
issue as a side-effect).
In both cases the GR user can do this themselves by wrapping either an Overlay or a Navigator inside of the navigator builder (I think?), so this is really a quality of life thing. To what degree should things "just work".
My personal opinion is that GR adding a Navigator is a bit too much implicit behavior, and is not always needed, but adding an Overlay is probably a nice pragmatic thing to do, with no real downsides, and is virtually always needed.
I don't actually think a GR user can fix the problem themselves with an Overlay; at least, I couldn't make that work. For the navigatorBuilder
to get called with the right stuff, I had to integrate the Overlay deep into the delegate. On the other hand, the Navigator
provides an Overlay
itself, so it sounds like I just did it wrong.
I think you might be right, I expected this to work, but it's not rebuilding things properly, despite calling markNeedsBuild
.
OverlayEntry? entry;
GoRouter goRouter = GoRouter(
navigatorBuilder: (_, __, navigator) {
entry ??= OverlayEntry(builder: (_) => MyScaffold(body: navigator));
entry!.markNeedsBuild();
return Overlay(initialEntries: [entry!]);
}
It's odd, breakpoints say the content is rebuilt, but the overlay is just never painted.
Wrapping Navigator is definitely most straightforward, and fixes all dialog and overlay issues, but it's unfortunately verbose:
GoRouter goRouter = GoRouter(
navigatorBuilder: (_, __, navigator) {
return Navigator(
onPopPage: (_, __) => false,
pages: [
MaterialPage(
child: MyScaffold(body: navigator),
)
]);
},
routes: [ ... ]
Could have an addExternalNavigator
option to GoRouter so we can get the nice default behavior out of the box, but it's not magic? The type of page shouldn't matter as I don't believe it is ever animated.
Then we'd get:
GoRouter goRouter = GoRouter(
addExternalNavigator: true,
navigatorBuilder: (_, __, navigator) => MyScaffold(body: navigator),
routes: [ ... ]
Although, I guess we could just bury the Navigator inside of MyScaffold
if we wanted, keeping the GoRouter
definition more readable.
Using GoRouter.navigatorBuilder with Overlay fails
I was trying use the
navigatorBuilder
to always wrap the content in a complex responsice scaffold wrapper and noticed that it fails if there is an Overlay widget in the widget in the wrapping widget in thenavigatorBuilder
.We can use the repo example
nested_nav.dart
to demonstrate the error. Wrap the text in thenavigatorBuilder
in the example with aTooltip
, eg so:This will result in this error:
Or in text form:
The error is of course well explained, but first felt a bit surprising, it is saying it does not have an
Overlay
in any parent, which it would get viaMaterialApp
or aNavigator
. Well we clearly have aMaterialApp
above it in the three, The built widget in thenavigatorBuilder
even gets the theme from it. However, aMaterialApp
created via the.router
interface apparently does not have aNavigator
a this stage, we don't see aNavigator
until much later in the tree, and theOverlay
is I think actually created by theNavigator
not by theMaterialApp
directly. (The error message is thus not entirely correct imo)This is a bit of a complication, well for my use case anyway.
This entire issue does of course go back to the not yet fully supported setup of nested navigation that I'm trying out different options for. I guess I in the
navigatorBuilder
I could include an extraNavigator
that then become a higher root than the GoRouter's Navigator, thus introducing the real missing nestedNavigator
.With Navigator 1.0 and using imperative routing that is actually how I solved nested navigation. The part in the widget tree that needed nested navigation, just contained a new
Navigator
and inserted via it navigated to widgets in that point in the tree.It seems I really need support for that in GoRouter to be able to build the routing I need. But I will return in another issue on that topic and relate it to the open nested navigation issue as well.
Conclusion
The current version of the
navigatorBuilder
inGoRouter
cannot be used to insert any widget that would work in a normalMaterialApp
, this was a surprise and not expected.