flutternavigatorobserversflutter-go-router

Flutter GoRouter Dynamically Change PageTransition Type with NavigatorObserver


I would like to dynamically change the transition used to navigate between pages.

To do so I created an enum that gives two different transition names TransitionType.fade and TransitionType.slide for example.

I then created an StatefulWidget surrounding the navigator. This stateful widget is updated to reflect the transition of the top most route (isCurrent) and all pages then follow that transition. That state can be updated at any time.

However this seems unnecessarily convoluted and I wondered if this could be done instead with a NavigatorObserver. All the Observer has to do is contain the state of which transition is to be used and allow that state to be set at any time. Is this possible?


Solution

  • I think an InheritedWidget would be the way to go instead of a NavigatorObserver.

    Here's a code sample that implement a GoRouterTransition widget which you can access using GoRouterTransition.of(context) or GoRouterTransition.maybeOf(context) to read and set your TransitionType (you'll have to wrap this widget on top of your MaterialApp):

    class GoRouterTransition extends StatefulWidget {
      const GoRouterTransition({
        super.key,
        required this.child,
      });
    
      final Widget child;
    
      @override
      State<GoRouterTransition> createState() => GoRouterTransitionState();
    
      static GoRouterTransitionState? maybeOf(BuildContext context) {
        return context
            .dependOnInheritedWidgetOfExactType<_TransitionTypeScope>()
            ?.data;
      }
    
      static GoRouterTransitionState of(BuildContext context) {
        final state = maybeOf(context);
        assert(state != null, 'No GoRouterTransition found in context');
        return state!;
      }
    }
    
    class GoRouterTransitionState extends State<GoRouterTransition> {
      TransitionType _type = TransitionType.fade;
      TransitionType get type => _type;
      set type(TransitionType value) => setState(() => _type = value);
    
      @override
      Widget build(BuildContext context) {
        return _TransitionTypeScope(
          data: this,
          child: widget.child,
        );
      }
    }
    
    class _TransitionTypeScope extends InheritedWidget {
      const _TransitionTypeScope({
        required this.data,
        required super.child,
      });
    
      final GoRouterTransitionState data;
    
      @override
      bool updateShouldNotify(_TransitionTypeScope oldWidget) {
        return data.type != oldWidget.data.type;
      }
    }
    

    You then just have to create your own custom GoRoute object which will build your page with the correct transition:

    class DynamicGoRoute extends GoRoute {
      DynamicGoRoute({
        required super.path,
        required GoRouterWidgetBuilder builder,
        super.routes = const <RouteBase>[],
      }) : super(
              pageBuilder: (context, state) {
                final type = GoRouterTransition.of(context).type;
    
                switch (type) {
                  case TransitionType.fade:
                    return FadePage<void>(
                      builder: (context) => builder(context, state),
                    );
                  case TransitionType.slide:
                    return SlidePage<void>(
                      builder: (context) => builder(context, state),
                    );
                }
              },
            );
    }
    

    You can try the complete sample on zapp.run