開発日誌Vol.1-3/Flutterでログインルーターの作成(ログイン機能編)

目次

go_routerの導入

STEP

go_routerインストール

flutterのルーティングを一括で管理できるgo_routerをインストールします。

flutter pub add go_router
STEP

MyRooterファイルの作成

ルーティングを1つのファイルで管理するために、MyRooterクラスとmyRooterProviderを作成します。

私は、MyRooterやmyRooterProviderでは無くて、WinRooterとwinRooterProviderとしています。好みで命名してください。

lib/win_rooter.dart
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,
    );
  }
}
lib/win_rooter.dart
final winRouterProvider = Provider(
  (ref) => GoRouter(
    routes: [
      GoRoute(
        path: '/',
        pageBuilder: (context, state) => const MaterialPage(child: TopPage()),
      ),
      GoRoute(
        path: '/login',
        pageBuilder: (context, state) => MaterialPage(child: LoginPage()),
      ),
    ],
  ),
);

リダイレクト

STEP

リダイレクトの作成

リダイレクトの考え方

リダイレクトは、まずログインの有無を確認します。そして、ログインしていれば、トップページへ、ログインしていなければ、ログインページへ移動するようにしなければなりません。

そのためには、ログイン情報を保存するためのデータが必要になります。ログイン情報をフラッシュメモリに保存すると、アプリを閉じたときに、ログイン情報が消えてしまいます。すると毎回ログイン処理を行わなければなりません。

そこで、デバイスのDBにログイン情報を保存し、その情報があれば、ログインをやり直す必要が無いのです。

そこで、前回登場したHiveを利用して、ログイン情報の呼び出しを行い、ログイン情報があれば、トップページへ、ログイン情報がなければ、ログインページへ移動するようにルーティングの指示を出します。

リダイレクトのロジック

リダイレクトのロジックをより詳細に記述すると次のようになります。

  1. Appの起動時に、HiveからAuthクラスのインスタンスを読み込む
  2. AuthクラスのisLoginプロパティをチェックする
    • 2-1. trueの場合 -> ユーザーはログイン済みなので、トップページにリダイレクト
    • 2-2. falseの場合 -> ログインしていないので、ログインページにリダイレクト
  3. ログインページでログイン処理が成功した場合
    • 3-1. Authインスタンスのtokenやuser情報などをセット
    • 3-2. isLoginをtrueに変更 3-3. AuthインスタンスをHiveに保存
  4. この後のページ遷移時は、最初にHiveからAuth状態を読み込み、2のロジックでリダイレクト判定を行う

以上のように、Hiveを利用してAuth情報を保持し、そこからログイン状態を判断することで、アプリの生命周期をまたいだ状態管理を実現できます。

STEP

Riverpodでリダイレクト管理

authProviderの利用

認証情報(前回作成したAuthモデル)を管理するために作成したプロバイダー(同じく前回作成したauthProvider)を利用してリダイレクトを作成します。

lib/providers/auth_provider.dart
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;: どのリダイレクト条件にも該当しない場合、リダイレクトは行われず、現在のナビゲーション操作はそのまま進行します。

このリダイレクトロジックにより、アプリケーションはユーザーの認証状態に基づいて適切なページに自動的にナビゲートすることができ、セキュリティを強化し、ユーザーエクスペリエンスを向上させることができます。

STEP

ルーターの全コード

ちょっと複雑になってきたので、go_routerの全コードを記述しておきます。

lib/win_router.dart
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,
    );
  }
}

関連ファイルの修正

STEP

MyAppクラスの修正

lib/main.dart
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(),
    );
  }
}
STEP

ログインボタンの編集

ログイン用のElevatedButtonを下記のように編集します。

lib/views/login_page.dart
// ...
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メソッドを非同期処理で記述します。

STEP

TopPageの作成

ログインしたときのTopPageを下記のように作成しておきます。トップページには、取りあえずログアウトボタンのみを作成します。

lib/views/top_page.dart
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メソッドに分離
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次