くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Flutterのgo_routerで現在のパスをwatch/listenする

Flutterのページ遷移には、go_routerを使ってるけど、
「ページが変わったらなにかしたい」みたいなのができないかなと、
いろいろ調べてみたときの備忘録(*´ω`*)

以下のIssueのコメントによいワークアラウンドがのっていた

試したときのバージョンは以下の通り

  • go_router: 12.1.3
  • flutter_riverpod: 2.5.1
  • hooks_riverpod: 2.5.1

やったこと

現在のパスが変更したら保存する

router.routerDelegate.addListener()を使うのが、
一番よいっぽい動きをするので、こんな感じに。

Riverpodflutter_hooksを使った書き方。

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:go_router/go_router.dart';

FutureOr<void> main() async {
  runApp(ProviderScope(child: const App()));
}

class App extends HookConsumerWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final router = GoRouter(/* ... */);

    // GoRouterのcurrent routeが変わったときの処理
    void handleRouteChange() {
      // UI描画中だとエラーになるので、描画後に処理
      WidgetsBinding.instance.addPostFrameCallback((_) {
        // 現在のRouteMatchListを取得
        final config = router.routerDelegate.currentConfiguration;
        
        // RouteMatchListから各種情報を取得
        final path = config.last.matchedLocation; // stack最後=現在のパス
        final pathParams = config.pathParameters;
        final queryParams = config.uri.queryParameters;
        
        // TODO: 取得した情報を保存する
      });
    }
    
    useEffect(() {
      // 初期化時にリスナーへ追加
      router.routerDelegate.addListener(handleRouteChange);
      return () {
        // dispose時にリスナーから削除
        router.routerDelegate.removeListener(handleRouteChange);
      };
    }, const []);

    
    return MaterialApp.router(
      // ...
      restorationScopeId: 'router',
      routerDelegate: router.routerDelegate,
      routeInformationProvider: router.routeInformationProvider,
      routeInformationParser: router.routeInformationParser,
    );
  }
}

pathは正確だけど、pathParamsqueryParamsは微妙な感じっぽい。。
うまく取得できていなかったり、空だったりする。。

現在のパスを保持するProvider

取得した情報を保持するProviderはこんな感じ。

// current_route.dart
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'current_route.freezed.dart';
part 'current_route.g.dart';

typedef RouteParams = Map<String, String>;

@freezed
class RouteInfo with _$RouteInfo {
  const RouteInfo._();
  const factory RouteInfo({
    required String path,
    Map<String, String>? pathParams,
    Map<String, String>? queryParams,
  }) = _RouteInfo;
}

@Riverpod(keepAlive: true)
class CurrentRoute extends _$CurrentRoute {
  @override
  RouteInfo build() {
    return RouteInfo(path: "/");
  }

  void setCurrent(
    String path, {
    Map<String, String>? params, 
    Map<String, String>? query,
  }) {
    state = state.copyWith(
      path: path,
      pathParams: params,
      queryParams: query,
    );
  }
}

CurrentRouteをwatch/listenしてなにかする

あとは、currentRouteProviderを使えばOK

ref.listen(currentRouteProvider, (previous, next) {
  // TODO: pathに応じて好きな処理をする
});

ほかに試したこと

よく見るこの辺りを試したけど、いろいろissueが上がっていたり、
ほしい情報がうまくとれなかったりで、だめだった。。

  • GoRouterRedirect内で取得
    • push/goのみで、popだと反応しない
  • NavigatorObserver
    • go_router周りのページ遷移が発火しない? ダイアログのpopなどのみ動作

以上!! これでだいぶ楽になるぞ。。。(*´ω`*)

参考にしたサイトさま