💡

[Firebase Authentication]ログイン済みなのにgetCurrentUserがnullになるときの対処

2022/03/09に公開

はじめに

Firebase AuthenticationでgetCurrentUserをつかったログインチェックを実装する際、ログイン済みなのにnullが返ることがあってつまずいたので、原因と対策を記載します。

原因

本事象の原因は、Authオブジェクトの初期化が完了していない段階でgetCurrentUserを利用したことでした。Firebase公式ドキュメントにも記載がありました(小さい...)。

注: currentUser が null になる原因としては、Auth オブジェクトの初期化が完了していないことも考えられます。オブザーバーを使用してユーザーのログイン ステータスを追跡している場合は、この状況に対処する必要はありません。

一般的にgetCurrentUserがnullを返すのはログインしていない状態ですが、実際には以下2つの状態が存在するということになります。

  • ログインしていない状態
  • ログインしている状態(Authオブジェクトの初期化が終わっていない場合)

対処

最も素直な対処としては、onAuthStateChangedでStreamをつかったログイン状態のチェックをすることです。わざわざgetCurrentUserを呼ばなくても、ユーザーのログインなどのイベントが発生したタイミングでuserが通知されます。

user_state.dart
FirebaseAuth.instance
  .authStateChanges()
  .listen((User? user) {
    if (user == null) {
      print('User is currently signed out!');
    } else {
      print('User is signed in!');
    }
  });

ただ私のユースケースではアプリ内(Flutter Web)で任意・複数のタイミングでgetCurrentUserを利用したいというニーズがあったことから、ルートレベルでStreamBuilderをかませてonAuthStateChangedをしておき、ユーザーを取得したいタイミングではこれまで通りgetCurrentUserする作りにしてみました。(もっとこうした方が良いなどあればアドバイスいただきたいです!)

main.dart
class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      // Call Auth.onAuthStateChanged inside.
      stream: loginController.authUserStream,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Center(
            child: CircularProgressIndicator(),
          );
        }
        return ChangeNotifierProvider<LoginViewModel>.value(
          value: loginController,
          child: MaterialApp.router(
            // Use go_router for Navigator2.0.
            routeInformationParser: _router.routeInformationParser,
            routerDelegate: _router.routerDelegate,
            title: 'My App',
          ),
        );
      },
    );
  }
  // 中略
  
  late final _router = GoRouter(
    routes: [
      GoRoute(
        path: Paths.login,
        pageBuilder: (context, state) => MaterialPage<void>(
          key: state.pageKey,
          child: const LoginScreen(),
        ),
      ),
    ],
    redirect: (state) {
      // Call Auth.getCurrentUser inside.
      loginController.checkLogin();
      if (loginController.isNotLoggedIn) {
        return Paths.login;
      }
      return null;
    },
  );
}

別クラスに切り出しているので分かりづらいですが、ルートのbuildメソッドのなかでonAuthStateChangedを呼んで初期化の完了をまち、リダイレクト処理内(go_routerredirectパラメータ)からgetCurrentUserを利用しています。

Discussion