見出し画像

【アプリ開発日記40週目】FlutterでGoogle認証を実装する

 Flutterでもログインにプッシュ通知、CRUD(ToDoリスト)など基本的な機能は実装できるようになってきました。でもその分、これがほしい!という部分も出てきてしまうもの。

 ウェブ然り、アプリでもログインといえばソーシャル認証がとにかく楽! 以前Next.jsでもnext-authを使用しましたが、やっぱりパスワードを覚える必要がないなど、UXもだいぶ変わってきます。

 ということでFlutterでもGoogle認証を実装していきます!

完成形

Googleボタンを押すと、そのままログインできます! 初回以外はパスワードの入力も不要。

簡易版

実機テストはもちろん、エミュレーターでも使えます。ワンタッチでログインできるようになるので、今更ながらもっと早めにやっておけば!と感じた機能でした。

作成中のアプリに実装

追加パッケージ

google_sign_in

sign_in_button

サインインの実装

 実装にあたっては、以下の記事を参考にさせていただきました! 公式ドキュメントもリアルタイムな情報が揃っていますが、複数のパッケージを組み合わせるときはやはりこういった記事が非常に分かりやすいです。

 備忘録として流れもそのままメモしておきます。最初は例のごとく、新しい練習用のアプリとFirebaseプロジェクトを用意しておくことが無難です!

 練習用のアプリを作成したら、まずFirebaseと接続。

firebase login
dart pub global activate flutterfire_cli
flutter pub add firebase_core
flutter pub add firebase_auth
flutterfire configure
Firebase「Authentication」で任意のログイン方法を有効にする(今回はメール/パスワードとGoogleの2つ)

Googleを追加した時にSDKを実行してくださいと言われます。「プロジェクトの設定」から

「マイアプリ」→「SDKの手順を確認する」でガイドが出てくるので、

  1. 更新されたgoogle-services.jsonをダウンロード、今回はandroid用なので「root/android/app」内に移動して、元ファイルを上書きします

  2. さらにガイドどおりに2つのbuild.gradleに追記

これでエミュレーションしても、まだエラーが出ます。

final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();

でエラーになってしまう場合は、Firebaseにフィンガープリント(SHA-1・SHA-256)を追加すると解決するそうです!

このフィンガープリントと呼ばれるアプリのデジタル証明書を取得するためには、以下の公式ドキュメントがわかりやすいです。

今回はWindowsで、デバック用証明書なので

keytool -list -v -keystore "C:\Users\{ユーザー名}\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
またはandroidディレクトリ内で
./gradlew signingReport
(両方試しましたが、同じSHAが取得できました)

をコマンドラインに入力すると、SHA-1・SHA-256が表示されます!

これをさきほどの「マイアプリ」内にある「フィンガープリントを追加」に入力。するとエミュレーターでもGoogle認証できるようになります!

SHA-1/256、それぞれ入力
sign_in_buttonのおかげでボタンも手間かからず作れる
ログイン成功!

データベースにユーザー情報を登録

 これで終わり! …と思ったのもつかの間、何度かログインとログアウトをカチャカチャしているうちに「あれ、このボタン、サインアップもサインインも同時にできるけど、そしたらFirestoreにユーザーのプロフィール保存できないよなぁ…」という事に気づきます。

 延長戦。データベースにアカウント情報がない場合は作成、ある場合は最終ログイン時刻を更新できるようにします!

 以下、サインインボタンの完成形です。

import (...略...)

class GoogleSignInButton extends StatefulWidget {
  (...略...)
}

class _GoogleSignInButtonState extends State<GoogleSignInButton> {

  // データベースにそのユーザーが保存されているか
  Future<void> checkExistence(email) async {
    final doc =
        await FirebaseFirestore.instance.collection('users').doc(email).get();

    final exists = doc.exists;
    debugPrint(exists.toString());
  }

  Future<UserCredential> signInWithGoogle() async {

    // 認証フローを起動する
    final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
    final GoogleSignInAuthentication? googleAuth =
        await googleUser?.authentication;
    debugPrint('ログイン完了!');

    // クレデンシャルを作成(idToken,accessTokenを取得できる)
    final credential = GoogleAuthProvider.credential(
      accessToken: googleAuth?.accessToken,
      idToken: googleAuth?.idToken,
    );
    setState(() {
      _isSigningIn = false;
    });
    // Google認証を通過した後、Firebase側にログイン ※emailが存在しなければ登録
    await FirebaseAuth.instance.signInWithCredential(credential);

    // ユーザー情報を更新
    User? newUser = FirebaseAuth.instance.currentUser!;
    final date = DateTime.now().toLocal().toIso8601String();
    debugPrint(newUser.email);

    // Firestoreに保存
    if (checkExistence(newUser.email).toString() == 'true') {
      FirebaseFirestore.instance.collection('users').doc(newUser.email).update({
        'last_login': date,
      });
      debugPrint('更新しました!');
    } else {
      FirebaseFirestore.instance.collection('users').doc(newUser.email).set({
        'email': newUser.email,
        'name': '',
        'note': '',
        'friends': [],
        'life': 3,
        'clear_count': 0,
        'exp': 0,
        'last_login': date,
        'created_at': date,
      });
      debugPrint('作成しました!');
    }

    // ホーム画面へ遷移
    await Navigator.of(context).pushReplacement(
      MaterialPageRoute(builder: (context) {
        return const Layout();
      }),
    );
    // サインインしたら、UserCredential を返す。
    return await FirebaseAuth.instance.signInWithCredential(credential);
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 16.0),
      child: _isSigningIn
          ? const CircularProgressIndicator(
              valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
            )
          : SizedBox(
              width: 200,
              height: 30,
              child: SignInButton(Buttons.google, onPressed: () {
                signInWithGoogle();
              }),
            ),
    );
  }
}

なるべく簡潔になるようにしましたが、全体の構成を把握するため、もう少しわかりやすくしてみます。

google_sign_in_button.dart
import (...略...)

class GoogleSignInButton extends StatefulWidget {
  (...略...)
}

class _GoogleSignInButtonState extends State<GoogleSignInButton> {

  // データベースにそのユーザーが保存されているか
  Future<void> checkExistence(email) async {
    (...略...)
  }

  // サインイン・サインアップ
  Future<UserCredential> signInWithGoogle() async {
    (...略...)
  }

  @override
  Widget build(BuildContext context) {
    return (...略...);
  }
}

 こんな感じです!

 ユーザー情報がデータベースにあるか判別する処理を作成し、サインインが初回かどうかを判定しています。

 稼働させ、「メール/パスワード」のときと同じようにログインできるようになりました!

現在作成中のアプリにも無事実装!

※詰まった点

サインインのとき、

// Google認証を通過した後、Firebase側にログイン
await FirebaseAuth.instance.signInWithCredential(credential);

がないと「FirebaseAuth.instance.currentUser」をしてもログインしていないことになってしまい、ユーザー情報を取得できません。

currentUserを使うときは、先に上記の処理を忘れないようにしてください!

おわりに

 以上の手順で、真新しいアプリにもさっとGoogle認証を追加できるようになります。一度実装の流れを体感すると想像以上にスムーズにできるので、ぜひ試してみてください!

 はじめは「build.gradleファイル?どこ…?」などと迷子になっていましたが、いずれもどの処理を追加するときにも触る設定ファイルなので、慣れてくればFlutterは加速度的に様々な機能を追加できるようになるイメージがあります。今でもbuild.gradleの使わない設定はよくわかりませんが。

 とは言え現在、端末に保存される歩数などの健康データを取得するのにとても四苦八苦しているので、まだ思い描いたようなアプリをそのまま形にできるには時間がかかりそうです。

 ではでは!

いいなと思ったら応援しよう!