🏍️

go_router_builderでリダイレクトの認証機能を実装する

2023/11/03に公開

普通のgo routerとの設定の違い

過去に書いた記事があるので、こちらを見ていただいた方がいいかなと思われます。
https://zenn.dev/flutteruniv_dev/articles/547dbbb7193ddf

go_router_builderで認証機能を実装しようとしましたが、最初は躓いて、中々うまくいきませんでした!

https://pub.dev/packages/go_router_builder

こちらが公式の解説ですね👇
go_router_builder: ^2.3.4に対応した方法ですね。

Redirection

Redirect using the location property on a route provided by the code generator:

redirect: (state) {
  final loggedIn = loginInfo.loggedIn;
  final loggingIn = state.matchedLocation == LoginRoute().location;
  if( !loggedIn && !loggingIn ) return LoginRoute(from: state.matchedLocation).location;
  if( loggedIn && loggingIn ) return HomeRoute().location;
  return null;
}

ルーティングを定義して、ファイルを自動生成するコマンドを実行すれば、リダイレクトの認証機能は一応作れます。知識があるのが前提なので、go router builder、riverpod、FirebaseAuthを学習された方むけの内容になっております。


riverpod generatorを使う場合はこのように、FirebaseAuthのプロバイダーを定義して使ってください。

genetatorバージョン
import 'package:firebase_auth/firebase_auth.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'auth.g.dart';

// flutter pub run build_runner watch --delete-conflicting-outputs

// FirebaseAuthを提供するProvider

FirebaseAuth firebaseAuth(FirebaseAuthRef ref) {
  return FirebaseAuth.instance;
}

// ログイン状態を監視するStreamを提供するProvider

Stream<User?> authStateChange(AuthStateChangeRef ref) {
  return ref.watch(firebaseAuthProvider).authStateChanges();
}

generatorを使用したルートの定義はこのようにします。

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

import '../../controller/auth/auth.dart';
import '../../view/auth/sign_in_page.dart';
import '../../view/page/first_page.dart';

part 'router.g.dart';

// flutter pub run build_runner build --delete-conflicting-outputs

GoRouter router(RouterRef ref) {
  final authState = ref.watch(authStateChangeProvider);
  return GoRouter(
      debugLogDiagnostics: true,
      routes: $appRoutes, // 自動生成されたファイルからパスを読み込む

      // リダイレクトの処理
      redirect: (context, state) {
        // ログインしているかどうかを判定
        final loggedIn = authState.asData?.value != null;
        // ログインしていない場合は、ログインページにリダイレクトする
        if (!loggedIn) {
          return const SignInRoute().location;
        }

        // ログインしている場合は、NextPageにリダイレクトする
        return const FirstRoute().location;
      },

      // 404ページを指定
      errorPageBuilder: (context, state) {
        return const MaterialPage(
            child: Scaffold(
          body: Center(
            child: Text('Page not found'),
          ),
        ));
      });
}

/// [SignInPageのルート]
<SignInRoute>(
  path: '/',
)
class SignInRoute extends GoRouteData {
  const SignInRoute();

  
  Widget build(BuildContext context, GoRouterState state) {
    return const SignInPage();
  }
}

/// [NextPageのルート]
<FirstRoute>(
  path: '/first',
)
class FirstRoute extends GoRouteData {
  const FirstRoute();

  
  Widget build(BuildContext context, GoRouterState state) {
    return const FirstPage();
  }
}

普通のプロバイダーを使う場合はこんな感じで書きます。

今までのriverpodの書き方
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:value_notifier_example/ui/page/first_page.dart';

import '../../provider/auth_provider.dart';
import '../../ui/page/next_page.dart';

part 'router.g.dart';

// flutter pub run build_runner build --delete-conflicting-outputs
final routerProvider = Provider((ref) {
  final authState = ref.watch(authStateProvider);
  return GoRouter(
    debugLogDiagnostics: true,
    routes: $appRoutes,// 自動生成されたファイルからパスを読み込む

    // リダイレクトの処理
    redirect: (BuildContext context, GoRouterState state) {
      // asData?.valueは、riverpodのStreamProviderの値を取得するプロパティ
      final bool loggedIn = authState.asData?.value != null;
      // ログインしていない場合は、ログインページにリダイレクトする
      if (!loggedIn) {
        return const FirstRoute().location;
      }

      // ログインしている場合は、NextPageにリダイレクトする
      return const NextRoute().location;

    },
    // 404ページを指定
    errorPageBuilder: (context, state) {
      return const MaterialPage(
          child: Scaffold(
            body: Center(
              child: Text('Page not found'),
            ),
          ));
    },
  );
});

<FirstRoute>(
  path: '/',
)
class FirstRoute extends GoRouteData {
  const FirstRoute();

  
  Widget build(BuildContext context, GoRouterState state) {
    return const FirstPage();
  }
}

/// [NextPageのルート]
<NextRoute>(
  path: '/next',
)
class NextRoute extends GoRouteData {
  const NextRoute();

  
  Widget build(BuildContext context, GoRouterState state) {
    return const NextPage();
  }
}

ログインのロジックは通常のコードを使います。匿名認証でもGoogle Sign Inでも特に変わりはなかったですね。

匿名認証を使ったパターン
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:value_notifier_example/provider/auth_provider.dart';

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

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('登録ページ'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(onPressed: () async {
              await ref.read(firebaseAuthProvider).signInAnonymously();
            }, child: const Text('登録')),
          ],
        )
      ),
    );
  }
}
Google Sign Inを使った例はこちら

ファイルは分けたほうがいいかなと思って、このようにクラスを作ってメソッドを定義してます。

import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../../const/logger/logger.dart';
import 'auth.dart';

part 'auth_controller.g.dart';

(keepAlive: true)
AuthController authController(AuthControllerRef ref) => AuthController(ref);

class AuthController {
  AuthController(this.ref);
  Ref ref;

  Future<UserCredential> signInWithGoogle() async {
    try {
      final googleUser = await GoogleSignIn().signIn();
      if (googleUser == null) {
        throw Exception('Google sign-in failed');
      }

      final googleAuth = await googleUser.authentication;

      final credential = GoogleAuthProvider.credential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      );

      return ref.read(firebaseAuthProvider).signInWithCredential(credential);
    } catch (e) {
      return Future.error(e);
    } finally {
      logger.d('signInWithGoogle');
    }
  }
}

パッケージを使ってGoogleのボタンをつけてるので、見た目もオシャレにしてます✨

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

import '../../controller/auth/auth_controller.dart';

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

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Go Router Builder'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            SignInButton(
              Buttons.google,
              onPressed: () async {
                await ref.read(authControllerProvider).signInWithGoogle();
              },
            ),
          ],
        ),
      ),
    );
  }
}

GithubでFirebaseAuthを使用したデモアプリを後悔しているので、こちらも参考にされてみてください。

https://github.com/sakurakotubaki/go-router-login

最後に

バージョンが上がると、go router builderが対応してないということもあったので、学習するのを避けていたのですが、最近携わっているアプリ開発で使う機会があり、キャッチアップできたので、ルートの設定が auto routerなるパッケージみたいに、コマンド打てば自動生成してくれたり、リダイレクトの処理も慣れれば難しそうではなかったので、個人でも使うようになりましたね。

Jboy王国メディア

Discussion