🐷

【Flutter】Firebase ログイン機能を状態管理するなら

2024/02/27に公開
13

こんにちは、タオルです。

続き
https://zenn.dev/yosefstar/articles/bf96bd8b6f1221

Firebaseを使っていて、ログイン機能を状態管理するなら、FirebaseAuthStateが便利です。

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

part 'auth_state_notifier.g.dart';


class AuthStateNotifier extends _$AuthStateNotifier {
  
  Stream<User?> build() {
    // FirebaseAuthのauthStateChangesをリッスンし、ユーザーの状態変更を監視
    return FirebaseAuth.instance.authStateChanges();
  }
}

FirebaseAuthStateはユーザーのログイン状態を保持しています。

ログイン状態があれば、ログイン入力をスキップ。なければ、ログイン画面入力ページに遷移。

import 'package:flutter/material.dart';
import 'package:flutter_question_answer/Pages/dashboard.dart';
import 'package:flutter_question_answer/Pages/home_page.dart';
import 'package:flutter_question_answer/auth_state_notifier.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class FirstPage extends StatelessWidget {
  const FirstPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Consumer(
          builder: (context, ref, child) {
       /// ここでfirebaseauthStateの監視を定義
            final userAsyncValue = ref.watch(authStateNotifierProvider);

            WidgetsBinding.instance.addPostFrameCallback((_) {
              userAsyncValue.maybeWhen(
                data: (user) {
                  if (user != null) {
                    Navigator.pushReplacement(
                      context,
                      PageRouteBuilder(
                        pageBuilder: (context, animation, secondaryAnimation) => HomePage(),
                        transitionsBuilder: (context, animation, secondaryAnimation, child) {
                          return FadeTransition(opacity: animation, child: child);
                        },
                        transitionDuration: const Duration(milliseconds: 500),
                      ),
                    );
                  } else {
                    Navigator.pushReplacement(
                      context,
                      PageRouteBuilder(
                        pageBuilder: (context, animation, secondaryAnimation) => DashboardPage(),
                        transitionsBuilder: (context, animation, secondaryAnimation, child) {
                          return FadeTransition(opacity: animation, child: child);
                        },
                        transitionDuration: const Duration(milliseconds: 500),
                      ),
                    );
                  }
                },
                orElse: () => const SizedBox.shrink(),
              );
            });

            return const SizedBox.shrink();
          },
        ),
      ),
    );
  }
}

ログインするか、ログアウトするか

 Consumer(
              builder: (context, ref, child) {
                // authStateProviderから認証状態を監視
                final userAsyncValue = ref.watch(authStateNotifierProvider);

                return userAsyncValue.when(
                  data: (User? user) {
                    if (user == null) {
                      // ユーザーがログインしていない場合、新規登録ボタンを表示
                      return ElevatedButton(
                        onPressed: () {
                          // 新規登録処理をここに実装
                        },
                        child: const Text('新規登録'),
                      );
                    } else {
                      // ユーザーがログインしている場合、ログアウトボタンを表示
                      return ElevatedButton(
                        onPressed: () async {
                          await FirebaseAuth.instance.signOut();
                        },
                        child: const Text('ログアウト'),
                      );
                    }
                  },
                  loading: () => const CircularProgressIndicator(),
                  error: (error, stack) => const Text('エラーが発生しました'),
                );
              },
            ),

Discussion

JboyHashimotoJboyHashimoto

StreamNotifirer使ってる人僕以外にいるんですね。珍しい。
StreamProviderだけでもできますけどね。処理が一個しかないならこっちの方が簡単なきがしますけどね。

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

part 'auth_provider.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();
}

addPostFrameCallback これは、Consumerだから使うんですかね。はじめてみたな。面白い。

タオルタオル

Jboyさん!いつも記事などを拝見しております!

コメントありがとうございます!
めちゃくちゃ嬉しいです!

また後ほど、返信いたします!

タオルタオル

AsyncNotifierのStreamを使ってるつもりで書いています!
addPostFrameCallback は、AI生成で出てきたコードなので、よく分かりません。すいません。

// FirebaseAuthを提供するProvider

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

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

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

こういった書き方もあるんですね!初心者なので、とても参考になります!

riverpod2以降は、riverpod使う時は、絶対extends _$Notifierを使うという雑な覚え方をしたのですが、
状況によっては、Providerを使う方がいい場合もあるのでしょうか?

riverpod2以降の書き方で足りない部分、Riverpod1のProviderの書き方を優先するときの判断がありそうで、riverpodの奥が深そうで辛いです。。泣

JboyHashimotoJboyHashimoto

addPostFrameCallback
これはフレームの描画が終わるまで、実行されないそうです。もし、ページが呼ばれた時に処理を実行したいなら、initStateかuseStateを使うと良いですね。

僕はあまり使ったことないので、活用例はあまり知らないですね。AWSとgo routerを組み合わせたコードで使ってる人がいましたね。
僕の記事はこれかな。
https://zenn.dev/jboy_blog/articles/211dabe9e0b4d2

Riverpod1のProviderの書き方を優先するときの判断がありそうで、riverpodの奥が深そうで辛いです。。泣
この書き方もまだ使われているますし、知識は必要なので、良いと思いますよ。

JboyHashimotoJboyHashimoto

まあ、StateNotifierでAsyncValueがわかれば、AsyncNotifierやStromeNotifierを理解できると思いますよ。
まずは、StreamProviderとFutureProviderも使いつつ慣れていくといいかなと思います。
Firebase使うだけなら、DocmentSnaphotとかQuerySnapshotがわかれば使えるようになると思います。

DocumentSnapshotなら、collection.doc('id')で、QuerySnapshotなら、collection('').getとかsnapshotになるかな。この辺は慣れるまで、わからないですね。AIに質問しながら、データ型に慣れていけるといいですね。
僕は自分で試しましたね。

タオルタオル

検索するといつもJboyさんの記事がたくさん出てくるので、勉強させてもらっています!

データ型になれるように、いろいろ触ってみようと思います!
コメントありがとうございます!嬉しいです!!

JboyHashimotoJboyHashimoto

公開されてないのか💦

感想

なるほど〜
やはり、AutoRouteは、画面遷移のコード書かないといけないんですね。go_routerとは違いますね。

class AuthNavigationController {
  static void navigateBasedOnAuthState(BuildContext context, WidgetRef ref) {
    ref.listen<AsyncValue<dynamic>>(authStateNotifierProvider, (_, state) {
      state.when(
        data: (user) {
          if (user != null) {
            context.router.replace(HomeRoute());
          } else {
            context.router.replace(const DashboardRoute());
          }
        },
        loading: () => {},
        error: (_, __) => {},
      );
    });
  }
}

それは置いておいて、

WidgetsBinding.instance.addPostFrameCallbackを使用して、ビルドプロセスが完了した後にナビゲーションを実行することができます。

デバッグすると、buildメソッドの中に処理書いていると何度も呼ばれますもんね。
WidgetsBinding.instance.addPostFrameCallbackを使えば、buildが終わった後に、画面遷移するから、アプリのパフォーマンス落とすようなことはしないってことかな。

とても勉強になりました\(^^)/

タオルタオル

早速返信ありがとうございます!

記事がまとまってなかったので、一旦下書きにしてましたがどこからかアクセスしてくれたんですね!

WidgetsBinding.instance.addPostFrameCallbackを使えば、buildが終わった後に、画面遷移するから、アプリのパフォーマンス落とすようなことはしないってことかな。

この通りです!
コミュニケーションしていただきありがとうございます!嬉しいです!!

JboyHashimotoJboyHashimoto

いえいえ。タオルさんは勉強熱心で良いことです。
ライフサイクルとかオブジェクト指向がわかれば良いコード書けるんだろうな〜と日々私は、探求しております。
頑張って継続してください。