FlutterでFirebase Authenticationを使ったGoogleアカウントログインを実装してみた

# はじめに
この記事は、[Flutter #2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/flutter-2)の7日目、[Firebase #2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/firebase_2)の7日目の記事です。

[Flutterアプリ](https://flutter.dev/)で[Firebase Authentication](https://firebase.google.com/docs/auth?hl=ja)を使ったGoogleアカウントログインを実装してみたので、2つのAdvent Calendarに殴り込みをかけてみました（＾ω＾ ≡ ＾ω＾）

[![Introducing Firebase Authentication](http://img.youtube.com/vi/8sGY55yxicA/0.jpg)](http://www.youtube.com/watch?v=8sGY55yxicA)

筆者はFlutterもFirebaseも初心者なので、お手柔らかにお願いします（予防線）

# Flutterプロジェクトの土台を作成する

以下、Flutterアプリの開発環境が整っている前提で話します。

```
[✓] Flutter (Channel stable, v1.9.1+hotfix.6, on Mac OS X 10.14.6 18G87, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.1)
[✓] Xcode - develop for iOS and macOS (Xcode 11.2.1)
[✓] Android Studio (version 3.4)
[✓] VS Code (version 1.40.2)
[✓] Connected device (1 available)
```


FlutterでHello worldを動かすまでの手順については、別途記事を書いております。Flutter未経験の方は合わせてご参考ください。

[FlutterでHello worldを動かすまでの環境構築手順（iOS, Android）](https://qiita.com/unsoluble_sugar/items/deaa129fb289922c8634)

とりあえず新規プロジェクトで土台を作成。
Flutterプロジェクト名は、`flutter_firebase_authentication`としました。

中身の実装については後述しますので、一旦寝かせといてください( ˘ω˘)ｽﾔｧ…

# Firebaseプロジェクトを作成する
まずはFirebaseプロジェクトを作成します。Firebaseの公式ガイドに沿って進めていきましょう。
[Flutter アプリに Firebase を追加する  |  Firebase](https://firebase.google.com/docs/flutter/setup?hl=ja)

[Firebase コンソール](https://console.firebase.google.com/?hl=ja)で [プロジェクトを作成] をクリック。
![スクリーンショット 2019-12-06 22.33.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6380/0f221cdb-00e6-5b18-1eba-61f6c1306c6b.png)
すでにFirebaseプロジェクトが存在する場合は [プロジェクトを追加] をクリックしてください。
![スクリーンショット 2019-12-07 00.35.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6380/01dc85d1-019a-e437-4310-fa4b5582b7cc.png)

既存のGCP（Google Cloud Platform）プロジェクトがある場合は、プルダウンからプロジェクトを選択できます。今回は新しいプロジェクト名を入力します。プロジェクト名は30文字以内となっていたので、`flutter-firebase-auth`としました。
![スクリーンショット 2019-12-06 22.36.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6380/65fbe44a-80d0-1d19-c980-d5b2e5d19aee.png)

アプリにGoogle Analyticsを連携させることもできますが、今回は有効化せず [プロジェクトを作成] をクリック。
![スクリーンショット 2019-12-06 22.38.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6380/85ee0a2e-e816-0a32-3756-9f4d569c4a6b.png)

しばらく待つと、Firebaseプロジェクトが作成されます。
![スクリーンショット 2019-12-06 22.39.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6380/98f0ba5b-7d1f-0de2-2a66-9fd60079bb3b.png)
[続行] をクリックして終わります。
## iOSアプリを構成する
続いてFlutterアプリがFirebaseプロジェクトに接続できるよう構成を行います。
今回は時間の都合上、iOSアプリの設定のみに留めます。Androidは自力で頑張ってください。

Firebaseコンソールにて、先ほど作成したFirebaseプロジェクトを開き、iOSアイコンをクリック。
![スクリーンショット 2019-12-06 22.41.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6380/e6a7a240-a0f2-b42c-9e98-1f773338f804.png)
ウィザードに従って設定を行なっていきます。

### アプリをFirebaseプロジェクトに登録する

アプリのバンドルIDを [iOS バンドル ID] に入力します。他項目は省略可ということで飛ばします。
![スクリーンショット 2019-12-06 22.56.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6380/d2aca78b-b545-0f2c-46bf-30b929cf0318.png)

バンドルIDはXcodeでアプリを開き、最上位のRunnerを選択するなどすれば確認できます。
`com.example.flutterFirebaseAuthentication`みたいな名前のやつです。
![スクリーンショット 2019-12-07 00.52.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6380/0b92238e-23e7-5929-c196-e93361dca61d.png)

バンドルIDを入力したら [アプリを登録] をクリックします。

### GoogleService-Info.plistをアプリに追加

[GoogleService-Info.plist をダウンロード] をクリックして、Firebase iOS 構成ファイルを取得。ダウンロードした`GoogleService-Info.plist`ファイルを、Flutterアプリの `Runner/Runner` ディレクトリにコピーします。

![スクリーンショット 2019-12-06 23.00.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6380/41bcb479-a28d-1430-8bc6-c3ffb876b45f.png)
Firebaseコンソールのウィザードに戻り、[次へ] をクリックして残りの手順（pod initなど）をすべてスキップして完了します。

## Firebase Authenticationの設定
最後にFirebase Authenticationの設定を行います。アプリに追加するプロダクトから [Authentication] を選択。
![スクリーンショット 2019-12-06 23.27.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6380/d987940c-daa9-7093-abef-a0147ca63302.png)

ログイン方法で [Google] を選択し、サポートメールを入力。[有効にする] のトグルをオンの状態にして保存します。
![スクリーンショット_2019-12-06_23_28_55_.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6380/b3b9f1d7-5b99-c96e-c8e0-db42aa5469fe.jpeg)

これでFirebaseコンソール側の作業は完了です。

# Flutterにプラグインを追加する
ここでようやくFlutterプロジェクトに戻ります。`pubspec.yaml`ファイルを開き、プラグインを追加しましょう。

今回はFirebase Authenticationを使ってGoogleアカウントのログインを実装するので、Firebase Core SDK用のFlutterFireプラグインを含め、3つのパッケージをインストールします。

- [firebase_core | Flutter Package](https://pub.dev/packages/firebase_core)
- [firebase_auth | Flutter Package](https://pub.dev/packages/firebase_auth)
- [google_sign_in | Flutter Package](https://pub.dev/packages/google_sign_in)

```pubspec.yaml 
dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^0.4.2+1
  firebase_auth: ^0.15.1
  google_sign_in: ^4.0.14
```

`pubspec.yaml`に追記して`flutter pub get`でインストール。


## Info.plistにCFBundleURLTypes追加

iOSのgoogle_sign_inで必要な設定を追加します。

`/ios/Runner/Info.plist`に`CFBundleURLTypes`を追加します。

```Info.plist
<!-- Put me in the [my_project]/ios/Runner/Info.plist file -->
<!-- Google Sign-in Section -->
<key>CFBundleURLTypes</key>
<array>
	<dict>
		<key>CFBundleTypeRole</key>
		<string>Editor</string>
		<key>CFBundleURLSchemes</key>
		<array>
			<!-- TODO Replace this value: -->
			<!-- Copied from GoogleService-Info.plist key REVERSED_CLIENT_ID -->
			<string>com.googleusercontent.apps.861823949799-vc35cprkp249096uujjn0vvnmcvjppkn</string>
		</array>
	</dict>
</array>
<!-- End of the Google Sign-in Section -->
```

`CFBundleURLSchemes`には、`GoogleService-Info.plist`内にある`REVERSED_CLIENT_ID`キーの値をコピーして貼り付けます。

# ログイン画面を作る

適当にログイン画面を作ります。詳細説明は省きますが、簡単な2つの画面を用意しました。

- ログイン画面
- プロフィール表示画面（ログイン後に遷移）

以下、画面の主要コード部分のみ抜粋。
エラー処理も雑なので、きちんと作るときは各自で何とかしてくれ（丸投げ）

## importとか

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

## ログイン画面
```main.dart
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final GoogleSignIn _googleSignIn = GoogleSignIn();
  final FirebaseAuth _auth = FirebaseAuth.instance;

  Future<FirebaseUser> _handleSignIn() async {
    GoogleSignInAccount googleCurrentUser = _googleSignIn.currentUser;
    try {
      if (googleCurrentUser == null) googleCurrentUser = await _googleSignIn.signInSilently();
      if (googleCurrentUser == null) googleCurrentUser = await _googleSignIn.signIn();
      if (googleCurrentUser == null) return null;

      GoogleSignInAuthentication googleAuth = await googleCurrentUser.authentication;
      final AuthCredential credential = GoogleAuthProvider.getCredential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      );
      final FirebaseUser user = (await _auth.signInWithCredential(credential)).user;
      print("signed in " + user.displayName);

      return user;
    } catch (e) {
      print(e);
      return null;
    }
  }

  void transitionNextPage(FirebaseUser user) {
    if (user == null) return;

    Navigator.push(context, MaterialPageRoute(builder: (context) => 
      NextPage(userData: user)
    ));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              RaisedButton(
                child: Text('Sign in Google'),
                onPressed: () {
                  _handleSignIn()
                    .then((FirebaseUser user) => 
                      transitionNextPage(user)
                  )
                  .catchError((e) => print(e));
                },
              ),
            ]
          ),
      ),
    );
  }
}
```

## プロフィール表示画面
```main.dart
class NextPage extends StatefulWidget {
  FirebaseUser userData;

  NextPage({Key key, this.userData}) : super(key: key);

  @override
  _NextPageState createState() => _NextPageState(userData);
}

class _NextPageState extends State<NextPage> {
  FirebaseUser userData;
  String name = "";
  String email;
  String photoUrl;
  final GoogleSignIn _googleSignIn = GoogleSignIn();

  _NextPageState(FirebaseUser userData) {
    this.userData = userData;
    this.name = userData.displayName;
    this.email = userData.email;
    this.photoUrl = userData.photoUrl;
  }

  Future<void> _handleSignOut() async {
    await FirebaseAuth.instance.signOut();
    try {
      await _googleSignIn.signOut();
    } catch (e) {
      print(e);
    }
    Navigator.pop(context);
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text("ユーザー情報表示"),
      ),
      body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Image.network(this.photoUrl),
              Text(this.name,
                style: TextStyle(
                  fontSize: 24,
                ),
              ),
              Text(this.email,
                style: TextStyle(
                  fontSize: 24,
                ),
               ),
              RaisedButton(
                child: Text('Sign Out Google'),
                onPressed: () {
                   _handleSignOut().catchError((e) => print(e));
                },
              ),
            ]),
        ),
      );
  }
```

# 実際に動かしてみた
上記コードでアプリを起動するとログイン画面が表示。
![スクリーンショット 2019-12-06 23.36.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6380/e7e67743-8d1f-008d-1ccc-a96ae980d7e1.png)

[Sign in Google] ボタンを押すと「Googleアカウントでログインするけどええか？」ダイアログが表示されるのでContinueする。
![スクリーンショット 2019-12-06 23.36.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6380/41ff596e-3cb0-4f51-3b8a-8cb69ba66eb7.png)

Googleアカウントのログイン画面が出てくるので、アカウント名とパスワードを入力。
![スクリーンショット_2019-12-06_23_36_37_.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6380/cc536980-c727-f719-bf63-1151bb426cf1.jpeg)


ログインが成功するとアカウント情報が表示される。
![スクリーンショット 2019-12-06 23.37.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6380/9db0d8f4-a470-26de-059b-501b033ed94d.png)

やったぜ。

# まとめ
というわけで、FlutterアプリのFirebase AuthenticationによるGoogleアカウントログインは、手順さえおさえれば簡単に実装できることがわかった。

本当はGitHubログインとTwitterログインの実装も盛り込みたかったのだが…アプリ登録まわりのフローがダル過ぎて挫折しました／(^o^)＼
メールアドレスや電話番号によるSMS認証の実装は超簡単っぽい。

Sign in with AppleのFlutter対応も秒読みの気配。
[[firebase_auth] Added Oauth for generic provider and solve auth for iOS devices by zariweyo · Pull Request #1526 · FirebaseExtended/flutterfire](https://github.com/FirebaseExtended/flutterfire/pull/1526)

## FlutterとFirebaseはいいぞ

Flutterは手頃なアプリがサクッと作れるので超楽しいですね（＾ω＾）
他のアカウントログインも余裕ある時にまた挑戦したいと思います。
![auth-providers.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6380/0653164b-19de-1cfa-aaa5-8b1484b3328a.png)
みんなもFlutterとFirebaseで最高のアプリを作ってくれよな！

[Flutter - Beautiful native apps in record time](https://flutter.dev/)
[Firebase](https://firebase.google.com/?hl=ja)

現場からは以上です。