torikatsu.dev

Flutterとかプログラミングとかガジェットとか書きます

【Flutter】flutterfire x riverpod x go_routerで認証ガードをスマートに実装する

はじめに

こんにちは、とりかつ(@torikatsu923)です。

以前、私はこんな記事を書きました。

torikatsu923.hatenablog.com

サインインのようにFirebaseの認証状態を変化させる関数を叩いてからFirebaseAuth.instance.authStateChanges()が反映されるまでに若干のタイムラグがあります
これが原因で以下のような場合にサインイン直後にcontext.goで認証ガードが行われているページへ遷移しようとすると、認証ガード内で最新の認証情報がとれず、画面遷移できないことがありました。

bad
bad

  • FirebaseAuth.instance.authStateChanges()をStreamProviderで管理している
  • GoRouterをriverpodのProviderで管理している
  • GoRouteのredirectでref.read()でFirebaseAuthを取得して認証ガードを行なっている

上記の記事ではこの問題を回避するため、authStateChagnes()の更新を待ってからcontext.go()する方法を紹介していました。

wait for update of authStateChange

あれからgo_routerを触っていたら、よりスマートな方法があることがわかりました。今回の記事ではその方法を紹介します。

認証ガードの方法

今回紹介するのはGoRouterのrefreshListenableを使う方法です。 authStateChangeGoRouterのrefreshListenableに渡すことで、context.go()を呼ばなくても認証状態が変化するたびにGoRouterが勝手にリダイレクトしてくれます。

    return GoRouter(
      routes: [...],
      ...
      refreshListenable: // ここにわたす,
    );

use refreshListenable
use refreshListenable

こんな感じでcontext.go('/home')をする必要がなくなります。

gorouter.dev

refreshListenableへの渡し方

以下のように`authStateChangeをStreamProviderで管理することを想定します。

final authProvider = StreamProvider<User?>(
  (ref) => FirebaseAuth.instance.authStateChanges(),
);

refreshListenableはListenableを受け取ります。そのためStreamProviderをそのままrefreshListenableへ渡すことはできません。

なので、以下のようにlistenSelfを使ってValueNotifierへ変換します。

class _AuthStateNotifier extends ValueNotifier<User?> {
  _AuthStateNotifier() : super(null);
  void change(User? v) => value = v;
}

final authStateNotifier = _AuthStateNotifier();

final authProvider = StreamProvider<User?>(
  (ref) {
    ref.listenSelf((_, v) => authStateNotifier.change(v.value));
    return FirebaseAuth.instance.authStateChanges();
  },
);

// ...
    return GoRouter(
      routes: [...],
      ...
      refreshListenable: authStateNotifier // pass Listenable
    );

あとは以下のようなリダイレクト関数をホーム画面、サインイン画面のGoRouteに設定すれば、認証情報の変化に伴って正しい遷移先へ勝手にリダイレクトされるようになります。

// use home route
String? authGuard(Reader read, [RouteGuard? guard]) {
  if (read(authProvider).value == null) {
    return '/signin';
  } else if (guard != null) {
    return guard();
  } else {
    return null;
  }
}

// use signin route
String? noAuthGuard(Reader read, [RouteGuard? guard]) {
  if (read(authProvider).value != null) {
    return '/home';
  } else if (guard != null) {
    return guard();
  } else {
    return null;
  }
}

おわりに

今回はGoRouterのrefreshListenableとFirebaseのauthStateChangesを組み合わせることでスマートに認証ガードを作る方法を紹介しました。 かなり説明を省いている箇所があります。フルサンプルを以下のリポジトリに用意したので、興味のある方は覗いてみてください。

github.com