go_router_builderでリダイレクトの認証機能を実装する
普通のgo routerとの設定の違い
過去に書いた記事があるので、こちらを見ていただいた方がいいかなと思われます。
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を使用したデモアプリを後悔しているので、こちらも参考にされてみてください。
最後に
バージョンが上がると、go router builderが対応してないということもあったので、学習するのを避けていたのですが、最近携わっているアプリ開発で使う機会があり、キャッチアップできたので、ルートの設定が auto routerなるパッケージみたいに、コマンド打てば自動生成してくれたり、リダイレクトの処理も慣れれば難しそうではなかったので、個人でも使うようになりましたね。
Discussion