csells/go_router

How do I switch widgets in my app based on the current navigation location in my app?

Closed this issue · 7 comments

A common use case in desktop or web applications is to use a single persistent page, and then have views change their internal children, depending on the current location of the router.

For example, a SideBar component, may render the MainNav child when on the home page, but then change it's child to DashboardNav when the location changes to /dashboard/*, and use SettingsMenu when location is /settings/*;

This can be done imperatively now, like:

Widget build(){
  String loc = GoRouter.of(context).location;
  late Widget child;
  if(loc.startsWith('/dashboard/')){
    child = DashboardNav();
  } else if(loc.startsWith('/settings/')){
    child = SettingsNav();
  } else{
    child = MainNav();
  }
  return AnimatedSwitcher(
     duration: ...,
     transitionBuilder: ...,
     child: child,
  );
}

but it seems like we could make a nice little AnimatedSwitcher type Widget, that is automatically driven by an ancestor GoRouter.

Widget build() => GoRouterWidgetSwitcher(
   duration: ...,
   transitionBuilder: ...,
   routes: [
      '/dashboard/*': DashboardNav(),
      '/settings/*': SettingsNav(),
      '*' : MainNav(),
   ]
)

This could make it quite convenient to create little sub-trees around the app that are bound to current path.

The tricky thing here is that it really needs some sort of wild-card or partial-match ability to be very useful. If we have to define every set of specific matches, it will just be easier to use imperative code and logical branching.

Maybe a predicate style api could work? First match wins? Null matches anything.

Widget build() => GoRouterWidgetSwitcher(
   duration: ...,
   transitionBuilder: ...,
   routes: [ 
      (loc) => loc.startsWith('/dashboard/'): DashboardNav()
      (loc) => loc.startsWith('/settings/'): SettingsNav()
      null : MainNav(),
   ]
)

@lulupointu any thoughts on the usefulness of this approach? We could basically use the navigatorBuilder to nest the main content area, and then sprinkle around the app other bindings to the location, that internally manage their own visual state as it relates to location.

I mean for GR to be the smallest, simplest thing to expose the capabilities of the Flutter Router API. Anything that can be done with standard Dart code in the existing framework of GR, I'd like to keep out. The fewer concepts the better.

The nice thing about this proposal imo was that that it did not add any complexity at all to the core GoRouter abstraction, it simply a helper widget, totally optional, that would fit nicely within the core lib and provide a quality of life boost for users.

Maybe this is something better implemented as a agnostic widget though. Presumably we could bind to the current location in flutter, without any reference to go_router or any specific routing lib and do the same thing.

Edit - Ya it looks like this widget could bind to SystemNavigator.routeInformationUpdated and then provide this handy widget switching behavior for any Router implementation.

I'd love a sample showing off such a thing to include with go_router.

Ended up putting this together and I think it turned out pretty cool:
https://pub.dev/packages/routed_widget_switcher

I think it should work with most Nav2 solutions, I built it on top of AnimatedSwitcher, so it uses that familiar syntax for transitions:

RoutedWidgetSwitcher(
    duration: ...,
    transitionBuilder: ...,
    builders: [
      PathBuilder('/', builder: (_) => const MainMenu()),
      PathBuilder('/dashboard', builder: (_) => const DashboardMenu()),
      PathBuilder('/settings', builder: (_) => const SettingsMenu()),
    ],
  ),

Uses pathToRegExp under the hood, but defaults to prefix mode, and adds support for a wildcard.

Nothing really specific to go_router, but it works with it, and I use GoRouter in my demos to drive the location changes.
https://github.com/gskinnerTeam/flutter-routed-widget-switcher/blob/master/example/lib/main.dart

I run into trouble running your package: gskinnerTeam/flutter-routed-widget-switcher#1

Fixed that issue, and also added 3 other routing implementations :)

Thx for the nudge to get it done!

uynM4qBfGT.mp4

very nice!