flutter firebase auth Googleプロバイダを使う

Play Consoleアカウントをお金を払って登録していることが条件

アプリごとに SHA1 フィンガープリントを追加する

https://developers.google.com/android/guides/client-auth?authuser=0

そのためには何でもいいので適当にアプリを作ってflutter build appbundle

それを内部テストでもいいのでリリースを作ってアップロード

サンプル作って動かしたらエラーが発生

サンプル
https://firebase.flutter.dev/docs/auth/social#google

エラー内容
com.google.android.gms.common.api.ApiException: 10

Play Consoleの アップロード鍵の証明書のところにあるSHA-1 証明書のフィンガープリントをFirebaseのコンソールに登録しないとダメみたいなので、登録し直し。

気を取り直して、サンプルを動かすとデバッグでコンソールにuser情報が出力されることを確認

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:firebase_auth/firebase_auth.dart';

Future<UserCredential?> signInWithGoogle() async {
  // Trigger the authentication flow
  final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
  if (googleUser == null) {
    return null;
  }

  // Obtain the auth details from the request
  final GoogleSignInAuthentication googleAuth = await googleUser.authentication;

  // Create a new credential
  final credential = GoogleAuthProvider.credential(
    accessToken: googleAuth.accessToken,
    idToken: googleAuth.idToken,
  );

  // Once signed in, return the UserCredential
  return await FirebaseAuth.instance.signInWithCredential(credential);
}

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MaterialApp(
    home: Scaffold(
      body: MyBody(),
    ),
  ));
}

class MyBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: ElevatedButton(
        child: Text('SIGN IN'),
        onPressed: () async {
          final user = await signInWithGoogle();
          print(user);
        },
      ),
    );
  }
}

この段階でAuthenticationにレコードができる

sign in, outをいい感じにする

単純にやると下記のようなコードになると思います。

// signInWithGoogleはさっきと変わりなし
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(ProviderScope(child: MyApp()));
}

final authStateChangesProvider = StreamProvider((ref) {
  return FirebaseAuth.instance.authStateChanges();
});

class MyApp extends HookConsumerWidget {
  @override
  Widget build(BuildContext context, ref) {
    return MaterialApp(home: AuthWidget());
  }
}

class AuthWidget extends HookConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ref.watch(authStateChangesProvider).when(
        data: (user) {
          if (user == null) return SignInScreen();
          return Scaffold(
            floatingActionButton: FloatingActionButton(
              onPressed: () async {
                await FirebaseAuth.instance.signOut();
              },
              child: Text('SIGN OUT'),
            ),
            body: WellcomeScreen(),
          );
        },
        error: (e, s, d) => Center(child: Text('$e')),
        loading: (d) => CircularProgressIndicator());
  }
}

class SignInScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: ElevatedButton(
        child: Text('SIGN IN'),
        onPressed: () async {
          // googleアカウントを選択するポップアップが起動して選択するとログインできる
          await signInWithGoogle();
        },
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('Wellcome'),
          ElevatedButton(
              onPressed: () async {
                // この方法だと遷移先でsign outしてもsignInページには移動しない
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => AnotherScreen()),
                );
              },
              child: Text('to another screen'))
        ],
      ),
    );
  }
}

class AnotherScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Center(child: Text('AnotherScreen')),
        ElevatedButton(
            onPressed: () async {
              // signOutが実行されてもこのページは表示されたまま
              await FirebaseAuth.instance.signOut();

              // 例えば苦し紛れにこの辺でNavigator.pushなどすると、
              // SignInScreenからandroidのbackボタンで
              // このAnotherScreenに戻ってこれる。
              // その時、firestoreなどに接続するようになってると
              // 権限の問題でエラーが発生したりする。
            },
            child: Text('SIGN OUT'))
      ],
    );
  }
}

ソースのコメントに書いてありますが、うまくsign outしません

単純にNavigatorを使うと上手くいきます。

class AuthWidget extends HookConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ref.watch(authStateChangesProvider).when(
        data: (user) {
          if (user == null) return SignInScreen();
          return Scaffold(
            floatingActionButton: FloatingActionButton(
              onPressed: () async {
                await FirebaseAuth.instance.signOut();
              },
              child: Text('SIGN OUT'),
            ),
            // ここにNavigatorを追加するだけで、AnotherScreenまで行った時に
            // fabでsign outすると、sign inページに遷移する
            body: Navigator(
              pages: const [
                MaterialPage(
                  key: ValueKey('WellcomeScreen'),
                  child: WellcomeScreen(),
                )
              ],
              onPopPage: (route, result) => route.didPop(result),
            ),
          );
        },
        error: (e, s, d) => Center(child: Text('$e')),
        loading: (d) => CircularProgressIndicator());
  }
}

https://github.com/na8esin/flutter2_open_project/tree/main/lib/src/auth