[Solved] Flutter: How to make a MaterialApp RouteAware

Question

Asked by knipknap on December 07, 2021 (source).

I am trying to subscribe a MaterialApp to a RouteObserver to track all pushes to the navigator. Unfortunately the subscribtion method of the observer requires a Route, which isn't known at the time where MaterialApp initializes.

final RouteObserver<ModalRoute<void>> routeObserver = RouteObserver<ModalRoute<void>>();

void main() {
  initSettings().then((_) {
    runApp(MyApp(
      home: Container(),
      navigatorObservers: [ routeObserver ],
    ));
  });
}

class MyApp extends MaterialApp with RouteAware {
  @override
  void didPush() {
    String? route = ModalRoute.of(context)!.settings.name;
    developer.log("didPush() $route");
 }
}

In other words, I don't know when and where I would subscribe the app to the RouteObserver.

ModalRoute? route = ModalRoute.of(context);
routeObserver.subscribe(this, route);

The official documentation for RouteObserver only shows an example on how to connect a widget, but that method doesn't work in a top-level widget like MaterialApp, because only child widgets have a route at the time where didChangeDependencies() is called.

Any idea where would be a good place to subscribe the top level widget?

Answer

Question answered by knipknap (source).

As suggested by pskink, RouteObserver does not support this. So a custom NavigatorObserver has to be implemented.

An additional thing to keep in mind is that this observer may be notified at times where Flutter is currently rendering, so pushing a new route needs to done after that is complete, using WidgetsBinding.

Here is how I did it:

void pushNamedReplace(navigator, String name, {Object? arguments}) {
  WidgetsBinding.instance!.addPostFrameCallback((_) {
    navigator.pushReplacementNamed(name, arguments: arguments);
  });
}

class GlobalNavigatorObserver extends RouteObserver<ModalRoute<Object?>> implements NavigatorObserver {
  @override
  void didReplace({ Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
    developer.log("GlobalNavigatorObserver.didReplace() from $oldRoute to $newRoute");
    if (newRoute == null || newRoute == oldRoute) {
      return;
    }
    didPush(newRoute, oldRoute);
  }

  @override
  void didPush(Route route, Route? previousRoute) {
    String prevRouteName = previousRoute == null ? "null" : previousRoute.settings.name as String;
    String? routeName = route.settings.name;
    developer.log("GlobalNavigatorObserver.didPush() from $prevRouteName to $routeName");

    switch (routeName) {
    case "/":
      SharedPreferences.getInstance().then((prefs) {
        if (prefs.containsKey('hostname')) {
          pushNamedReplace(route.navigator!, '/connect');
        }
        else {
          pushNamedReplace(route.navigator!, '/init');
        }
      });
      return;

    case "/connect":
      // ...
      return;

    ....
  }
}

final GlobalNavigatorObserver routeObserver = GlobalNavigatorObserver();

The full implementation is here.

FLUTTER
SHARE: