NavigationRail with go_router
Asked Answered
S

1

8

I'm developing a web/desktop application that has a fairly standard UI layout involving a NavigationRail on the left and a content pane taking up the remainder of the screen.

enter image description here

I've reciently added go_router so I can properly support URLs in web browsers, however in doing so I have lost the ability to have any form of transition/animation when moving between pages as calling context.go() causes a hard cut to the next page.

Theres also the issue that go_router routes have to return the full page to be rendered meaning I need to include the navigation rail on every page rather than each page being just the content relevant to that page. I believe this is also the main reason all animations are broken, because clicking a link effectively destroys the current navigation rail and builds a new one for the new page

I couldn't see anything in go_router but is there any form of builder API available that can output and refresh a single section of the page? I'm thinking of something like bloc's BlocBuilder which listens for state changes and rebuilds just the widget its responsible for when a change occures.

Alternatively, is there a way to update the current URL without rebuilding the whole page?

Or is go_router just not capable of what I'm after, and if so, are there any alternatives that can do this?

The overall effecti I'm after is similar to the material site https://m3.material.io/develop Clicking around the various buttons feels like you are navigating around within an app rather than clicking on links and loading new pages

Thanks for your help

Spirketing answered 23/10, 2022 at 12:52 Comment(3)
See also github.com/flutter/flutter/issues/99095Indisposition
I'm thinking the right thing to do would be putting the navigation rail above the Navigator via MaterialApp.router(builder:. However, this causes an No GoRouter found in context error, as discussed here, for which I do not yet have a workaround.Indisposition
Looks like you can work around the No GoRouter found in context by using routerConfig.routerDelegate.navigatorKey.currentContext as your .go(context) context. I was able to get things working this way. Hopefully I'll be able to summarize an answer in a few days. If I don't, an implementation should soon be available in my Dansdata Portal repositoryIndisposition
E
2

I solved this with a Shell Route. I got the idea when going through the go_router 5 migration guide. I saw that they had a navigatorBuilder property in which they were wrapping every route in a Scaffold with AppBar. And it said to migrate:

Before migration:

final GoRouter router = GoRouter(
 routes: <GoRoute> [
   GoRoute(
     path: '/',
     builder: (_, __) => const Text('/'),
   ),
   GoRoute(
     path: '/a',
     builder: (_, __) => const Text('/a'),
   )
 ],
 navigatorBuilder: (BuildContext context, GoRouterState state, Widget child) {
   return Scaffold(
     appBar: AppBar(title: Text(state.location)),
     body: child,
   );
 }
);

After Migration:

final GoRouter router = GoRouter(
 routes: <GoRoute> [
   ShellRoute(
     builder: (_, GoRouterState state, child) {
       return Scaffold(
         body: child, 
         appBar: AppBar(title: Text(state.location)),
       );
     },
     routes: [
       GoRoute(
         path: 'a',
         builder: (_, __) => const Text('a'),
       ),
       GoRoute(
         path: 'b',
         builder: (_, __) => const Text('b'),
       )
     ],
   ),
 ],
);

Now every route in the ShellRoute.routes property will be wrapped with that Scaffold and AppBar, it will not rebuild upon navigation, so will behave as a persistent AppBar, NavigationBar, or whatever should. Now it shows the page transition animations when you do context.go('some_route') (for example from a NavigationBar in the ShellRoute). You also don't run into the issue Felix ZY was having, accessing GoRouter from the context.

A NavigationRail would just be like this in the shell route:

ShellRoute(
 builder: (_, GoRouterState state, child) {
   return Scaffold(
  body: Column(children:[Row(
    children: <Widget>[
      NavigationRail(....)]),
  child,]));
});

}

Enhance answered 4/1, 2023 at 4:2 Comment(2)
In my case I show a BottomNavigationBar or a NavigationRail based on the display's orientation. The BottomNavigationBar works fine (doesn't re-render) but the NavigationRail has the issue. In your answer, you show an AppBar working fine, but that's not the issue in question.Boysenberry
@FilippoOrrù I have added what the code would be for NavigationRail in the solution. You can add conditional rendering in there too. The rebuilding is not affected by having a NavigationRail. Rebuilding is controlled by your widget state. I have wrapped my Scaffold with ResponsiveBuilder a third party package to rebuild on different screen sizes which causes my shell route to rebuild and conditionally render with a Scaffold.appBar or Scaffold.bottomNavigationBarEnhance

© 2022 - 2024 — McMap. All rights reserved.