go_routerの導入
go_routerインストール
flutterのルーティングを一括で管理できるgo_routerをインストールします。
flutter pub add go_router
MyRooterファイルの作成
ルーティングを1つのファイルで管理するために、MyRooterクラスとmyRooterProviderを作成します。
class WinRouter extends HookConsumerWidget {
const WinRouter({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp.router(
routeInformationParser:
ref.watch(winRouterProvider).routeInformationParser,
routerDelegate: ref.watch(winRouterProvider).routerDelegate,
routeInformationProvider:
ref.watch(winRouterProvider).routeInformationProvider,
);
}
}
final winRouterProvider = Provider(
(ref) => GoRouter(
routes: [
GoRoute(
path: '/',
pageBuilder: (context, state) => const MaterialPage(child: TopPage()),
),
GoRoute(
path: '/login',
pageBuilder: (context, state) => MaterialPage(child: LoginPage()),
),
],
),
);
リダイレクト
リダイレクトの作成
リダイレクトの考え方
リダイレクトは、まずログインの有無を確認します。そして、ログインしていれば、トップページへ、ログインしていなければ、ログインページへ移動するようにしなければなりません。
そのためには、ログイン情報を保存するためのデータが必要になります。ログイン情報をフラッシュメモリに保存すると、アプリを閉じたときに、ログイン情報が消えてしまいます。すると毎回ログイン処理を行わなければなりません。
そこで、デバイスのDBにログイン情報を保存し、その情報があれば、ログインをやり直す必要が無いのです。
そこで、前回登場したHiveを利用して、ログイン情報の呼び出しを行い、ログイン情報があれば、トップページへ、ログイン情報がなければ、ログインページへ移動するようにルーティングの指示を出します。
リダイレクトのロジック
リダイレクトのロジックをより詳細に記述すると次のようになります。
- Appの起動時に、HiveからAuthクラスのインスタンスを読み込む
- AuthクラスのisLoginプロパティをチェックする
- 2-1. trueの場合 -> ユーザーはログイン済みなので、トップページにリダイレクト
- 2-2. falseの場合 -> ログインしていないので、ログインページにリダイレクト
- ログインページでログイン処理が成功した場合
- 3-1. Authインスタンスのtokenやuser情報などをセット
- 3-2. isLoginをtrueに変更 3-3. AuthインスタンスをHiveに保存
- この後のページ遷移時は、最初にHiveからAuth状態を読み込み、2のロジックでリダイレクト判定を行う
以上のように、Hiveを利用してAuth情報を保持し、そこからログイン状態を判断することで、アプリの生命周期をまたいだ状態管理を実現できます。
Riverpodでリダイレクト管理
authProviderの利用
認証情報(前回作成したAuthモデル)を管理するために作成したプロバイダー(同じく前回作成したauthProvider)を利用してリダイレクトを作成します。
redirect: (context, state) {
final isLogin = ref.watch(authProvider).isLogin;
if (!isLogin) {
return state.uri.path == '/login' ? null : '/login';
} else if (state.uri.path == '/login') {
return '/';
}
return null;
},
リダイレクト処理の解説
- final isLogin = ref.watch(authProvider).isLogin;: ここで、authProviderからログイン状態(isLogin)を取得しています。ref.watch() は、指定されたプロバイダーの現在の状態を監視するために使われます。
- if (!isLogin) { … }: ユーザーがログインしていない場合の条件です。ログインしていないユーザーを /login ページにリダイレクトします。
- state.uri.path == ‘/login’ ? null : ‘/login’;: 現在のルートのパスが /login であれば、何もしません(nullを返します)。それ以外の場合は、’/login’ にリダイレクトします。
- state.uri.path == ‘/login’ ? null : ‘/login’;: 現在のルートのパスが /login であれば、何もしません(nullを返します)。それ以外の場合は、’/login’ にリダイレクトします。
- else if (state.uri.path == ‘/login’) { return ‘/’; }: この行は、既にログインしているユーザーが /login ページにアクセスしようとした場合、ホームページ(’/’)にリダイレクトすることを意味します。
- return null;: どのリダイレクト条件にも該当しない場合、リダイレクトは行われず、現在のナビゲーション操作はそのまま進行します。
このリダイレクトロジックにより、アプリケーションはユーザーの認証状態に基づいて適切なページに自動的にナビゲートすることができ、セキュリティを強化し、ユーザーエクスペリエンスを向上させることができます。
ルーターの全コード
ちょっと複雑になってきたので、go_routerの全コードを記述しておきます。
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'providers/auth_provider.dart';
import 'views/login_page.dart';
import 'views/top_page.dart';
final winRouterProvider = Provider(
(ref) {
return GoRouter(
routes: [
GoRoute(
path: '/',
pageBuilder: (context, state) => const MaterialPage(child: TopPage()),
),
GoRoute(
path: '/login',
pageBuilder: (context, state) => MaterialPage(child: LoginPage()),
),
],
redirect: (context, state) {
final isLogin = ref.watch(authProvider).isLogin;
if (!isLogin) {
return state.uri.path == '/login' ? null : '/login';
} else if (state.uri.path == '/login') {
return '/';
}
return null;
},
);
},
);
class WinRouter extends HookConsumerWidget {
const WinRouter({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final router = ref.watch(winRouterProvider);
return MaterialApp.router(
debugShowCheckedModeBanner: false,
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
routeInformationProvider: router.routeInformationProvider,
);
}
}
関連ファイルの修正
MyAppクラスの修正
class MyApp extends HookConsumerWidget {
const MyApp({
Key? key
}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return const MaterialApp(
initialRoute: '/',
debugShowCheckedModeBanner: false,
home: WinRouter(),
);
}
}
ログインボタンの編集
ログイン用のElevatedButtonを下記のように編集します。
// ...
Widget build(BuildContext context, WidgetRef ref) {
final auth = ref.watch(authProvider.notifier); //追加
// ...
ElevatedButton(
onPressed: () async {
await auth.login(
emailController.text, passwordController.text);
},
child: const Text(
'ログイン',
style: TextStyle(fontSize: 18),
),
),
// ...
}
authProviderのloginメソッドを非同期処理で記述します。
TopPageの作成
ログインしたときのTopPageを下記のように作成しておきます。トップページには、取りあえずログアウトボタンのみを作成します。
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../providers/auth_provider.dart';
class TopPage extends HookConsumerWidget {
const TopPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final auth = ref.watch(authProvider.notifier);
return Scaffold(
appBar: AppBar(
title: const Text('WinRoad'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('TopPage'),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => _logout(auth),
child: const Text('ログアウト'),
),
],
),
),
);
}
Future<void> _logout(AuthNotifier auth) async {
// ログアウト処理
await auth.logout();
// 必要に応じてUIの更新やナビゲーション処理をここに追加
}
}
- 10行目:authProviderからnotifireを参照しています。
- 22行目:非同期のlogout処理を_logoutメソッドに分離
コメント