🚀

TypedShellRouteでスタックごとに、observerを適応する

2024/08/09に公開
2

この記事では、Flutter(Dart) で TypedShellRouteを使用する際に、GoRouterobserverがうまく追跡できなくなる問題を解決する方法を紹介します。特に、タブごとに異なるNavigatorを使用する場合に発生する問題について詳しく解説します。

こんな人におすすめ

・@TypedShellRouteを使っている
・NavigationKeyが変わった瞬間 Observerが追跡されなくなった
https://pub.dev/documentation/go_router/latest/go_router/TypedShellRoute-class.html

⭐️ NavigationKeyがかわってもObserverの追跡が可能になります。

初めに

一般的な gorouterの observerは以下のような感じでしょうか。
しかし、今回紹介するTypedShellRoute で同じようにobserversを適応すると、navigationkeyが切り替わると、observerの追跡が切れてしまいます。

理由は、TypedShellRouteのように複数のNavigatorスタックをサポートする場合、それぞれのスタックに対応するNavigatorが別々のnavigatorKeyを持つことになります。このため、GoRouterの内部でnavigatorKeyが切り替わると、別のNavigatorがアクティブになり、最初に設定されたobserverが新しいNavigatorを追跡しなくなるという問題が発生するのです。

これを防ぐためには、

💡navigatorKeyが切り替わる際に、observerを新しいNavigatorインスタンスに関連付ける必要があります。

return GoRouter(
    navigatorKey: navigationKey,
    initialLocation: SplashPage.pagePath,
    observers: [transitionObserver],

手順

具体的には以下の手順で実装できます。
また、issueはこちらにあります。
https://github.com/flutter/flutter/issues/143869

1. ページ遷移を追跡する TransitionObserver クラスを定義する

enum AppTransitionType {
  push,
  pop,
}

class TransitionObserver extends NavigatorObserver {
  /// ページ遷移を追跡する
  static FirebaseAnalytics analytics = FirebaseAnalytics.instance;
  
  // 新しいルートがナビゲータースタックにプッシュされたとき
  @override
  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
    super.didPush(route, previousRoute);
    _onTransition(route, previousRoute, AppTransitionType.push);
  }

  // 現在のルートがナビゲータースタックからポップされたとき
  @override
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
    super.didPop(route, previousRoute);
    _onTransition(route, previousRoute, AppTransitionType.pop);
  }
 
  // .... (お好みで didRemoveなど追加)

  void _onTransition(
    Route<dynamic> route,
    Route<dynamic>? previousRoute,
    AppTransitionType transitionType,
  ) async {
    /// 表示されたページ名を取得
    final pageName = route.settings.name ?? 'unknown';
    logger.i('pageName: $pageName, ${transitionType.name}');
    await analytics
      ..logEvent(name: 'page_transition', parameters: {
        'page_name': pageName,
        'transition_type': transitionType.name,
      });
  }
}

2. ブランチごとに Observerを仕込む

/// bottom tab1 branch
class FirstBranch extends StatefulShellBranchData {
  const FirstBranch();

  static final List<NavigatorObserver> $observers = [
    TransitionObserver(),  → これ
  ];
  static final GlobalKey<NavigatorState> $navigatorKey = firstNavigatorKey;
}

/// bottom tab2 branch
class SecoundBranch extends StatefulShellBranchData {
  const SecoundBranch();

  static final List<NavigatorObserver> $observers = [
    TransitionObserver(), → これ
  ];
  static final GlobalKey<NavigatorState> $navigatorKey = secoundNavigatorKey;
}

上記、issue通りにやったら、きちんとタブが切り替わっても Observerで追跡できました!

まとめ

マーケ側から、見られているページが多いのはどこか情報収集したいといった要望があった場合、対応できるようなりました。これはかなり重要です。

最近は、TypedShellRoute がとっても気に入ってます。
タイプセーフに実装できるのと、タブが切り替わっても前の状態がのこり続けるのが最高ですよね⭐️
ぜひ使ってみてください!

Discussion

DiegoDiego

最近同じ問題にぶつかったのですが、
StatefullShellBranchを使用している場合もう一つ問題がありまして、
ブランチ間を移動したときに、すでに一度ブランチが生成(インスタンス化)されている場合はobserverがdidPushなどが発火されません。
(ブランチ間の移動なので、pushじゃないのは当たり前ですが、、)
※ブランチ内の移動は記録されます!

analyticsで画面移動を記録したい場合は考慮が必要になるかなと思います。。

renren

コメントありがとうございます😊!

今回は ブランチ間の移動は対象外としてます。