[Solved] Flutter: How to make a MaterialApp RouteAware


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((_) {
      home: Container(),
      navigatorObservers: [ routeObserver ],

class MyApp extends MaterialApp with RouteAware {
  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?


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 {
  void didReplace({ Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
    developer.log("GlobalNavigatorObserver.didReplace() from $oldRoute to $newRoute");
    if (newRoute == null || newRoute == oldRoute) {
    didPush(newRoute, oldRoute);

  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');

    case "/connect":
      // ...


final GlobalNavigatorObserver routeObserver = GlobalNavigatorObserver();

The full implementation is here.