FlutterでFirebase Authenticationを使ってみた

すべてのAWSが5%オフ

大阪オフィスの山田です。通勤が唯一の運動になっていて運動不足が加速しています。今回は前回のチャットアプリに、Firebase Authenticationを使ってログイン機能をつけてみます。Firebase Authenticationを使うこと自体が初めてです。

今回やること

前回作ったチャットアプリは、誰が投稿したかわからない状態だったので、Firebase Authenticationを使い、ログイン機能を追加して誰の投稿かわかるようにします。ログインにはGoogleアカウントを使用します。こちらのリポジトリを部分的に参考にしています。

完成イメージ

PR満足度98%

開発環境

flutter doctor

1
2
3
4
5
6
7
8
9
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel beta, v0.5.1, on Mac OS X 10.13.5 17F77, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK 28.0.1)
[✓] iOS toolchain - develop for iOS devices (Xcode 9.4)
[✓] Android Studio (version 3.1)
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
[!] VS Code (version 1.26.1)
[✓] Connected devices (1 available)

flutter --version

1
2
3
4
Flutter 0.5.1 • channel beta • https://github.com/flutter/flutter.git
Framework • revision c7ea3ca377 (3 months ago) • 2018-05-29 21:07:33 +0200
Engine • revision 1ed25ca7b7
Tools • Dart 2.0.0-dev.58.0.flutter-f981f09760

準備

各プラットフォームの設定

Android、iOS、各プラットフォームのプロジェクトをFirebase上に作成し、google-service.jsonGoogleService-Info.plistをプロジェクトに追加します。こちらの記事の「準備」の章で各プラットフォームで設定をしていますのでご参照ください。

Firebase Authenticationの準備

最初にFirebase Authenticationを使えるようにします。Firebase Consoleにログインして、左のペインからAuthenicationを選択します。

Authenticationの「ログイン方法」をクリックしてGoogle認証を使用できるようにします。

プロジェクトのサポートメールが必須なので、メールアドレスを入力します。

AndroidでGoogleアカウント認証を行う場合は、アプリのSHA-1フィンガープリントを設定する必要があります。ターミナルで以下のコマンドを実行して、SHA-1フィンガープリントを表示してください。

1
keytool -alias androiddebugkey -keystore ~/.android/debug.keystore -list -v -storepass android

Firebase Consoleに戻り、設定からAndroidアプリの設定に移動し、SHA-1フィンガープリントを設定します。

これでFirebase Authenticationの準備は終了です。

iOSアプリの準備

以下の記述をinfo.plistに記述する必要があります。詳細はこちら。GoogleServices-Info.plistREVERSED_CLIENT_IDを抜き出して設定してください。

1
2
3
4
5
6
7
8
9
10
11
12
13
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <!-- TODO Replace this value: -->
            <!-- Copied from GoogleServices-Info.plist key REVERSED_CLIENT_ID -->
            <string>your_reversed_client_id</string>
        </array>
    </dict>
</array>

実装

前準備

前回作ったチャットアプリの続きから始めます。

pluginのインポート

firebase_authgoogle_sign_inを使用します。pubspec.yamlに以下の記述を追加します。(2018/08/27時点のバージョンを使用しています)

1
2
3
dependencies:
  firebase_auth: ^0.5.19
  google_sign_in: ^3.0.4

ソースコードにはこちらを記述してimportします、

1
2
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';

Googleアカウントでログインできるボタンを用意

まず、クラス変数として、現在ログインしているユーザーを定義します。

1
FirebaseUser _user;

ログインしなければ、ログインボタンを表示して、ログインしていればチャットを表示します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: new Text("Firebase Chat")),
    body: Container(
        child: _user == null ? _buildGoogleSignInButton() : _buildChatArea()),
  );
}
 
Widget _buildGoogleSignInButton() {
  return Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Center(
          child: RaisedButton(
        child: Text("Google Sign In"),
        onPressed: () {
          _handleGoogleSignIn().then((user) {
            setState(() {
              _user = user;
            });
          }).catchError((error) {
            print(error);
          });
        },
      )),
    ],
  );
}

_handleGoogleSignInメソッドで実際にログインするフローに入ります。(うーん、フォーマッターをかけているのにインデントがおかしい。。。)ログインに成功したら、クラス変数にユーザーをセットして再描画します。ログインできたら、チャットの画面が表示されます。

Google SignIn

こちらを参考にログイン処理を記述します。

1
2
3
4
5
6
7
8
9
10
11
12
13
final _googleSignIn = new GoogleSignIn();
final _auth = FirebaseAuth.instance;
 
Future<FirebaseUser> _handleGoogleSignIn() async {
  GoogleSignInAccount googleUser = await _googleSignIn.signIn();
  GoogleSignInAuthentication googleAuth = await googleUser.authentication;
  FirebaseUser user = await _auth.signInWithGoogle(
    accessToken: googleAuth.accessToken,
    idToken: googleAuth.idToken,
  );
  print("signed in " + user.displayName);
  return user;
}

チャットメッセージのデータ構造

チャットメッセージのデータ構造は以下のようにしました。メッセージ、ユーザー名、ユーザーのメールアドレス、ユーザーのアバターUrlを持ちます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class ChatEntry {
  String key;
  DateTime dateTime;
  String message;
  String userName;
  String userEmail;
  String userImageUrl;
 
  ChatEntry(this.dateTime, this.message, FirebaseUser _user) {
    this.userName = _user.displayName;
    this.userEmail = _user.email;
    this.userImageUrl = _user.photoUrl;
  }
 
  ChatEntry.fromSnapShot(DataSnapshot snapshot)
      : key = snapshot.key,
        dateTime =
            new DateTime.fromMillisecondsSinceEpoch(snapshot.value["date"]),
        message = snapshot.value["message"],
        userName = snapshot.value["user_name"],
        userEmail = snapshot.value["user_email"],
        userImageUrl = snapshot.value["user_image_url"];
 
  toJson() {
    return {
      "date": dateTime.millisecondsSinceEpoch,
      "message": message,
      "user_name": userName,
      "user_email": userEmail,
      "user_image_url": userImageUrl,
    };
  }
}

チャット画面のレイアウト

少し長いですが、実装です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
Widget _buildChatArea() {
  return Column(
    children: <Widget>[
      Expanded(
        child: ListView.builder(
          padding: const EdgeInsets.all(16.0),
          itemBuilder: (BuildContext context, int index) {
            return _buildRow(index);
          },
          itemCount: entries.length,
        ),
      ),
      Divider(
        height: 4.0,
      ),
      Container(
          decoration: BoxDecoration(color: Theme.of(context).cardColor),
          child: _buildInputArea())
    ],
  );
}
 
Widget _buildRow(int index) {
  ChatEntry entry = entries[index];
  return Container(
      margin: EdgeInsets.only(top: 8.0),
      child: _user.email == entry.userEmail
          ? _currentUserCommentRow(entry)
          : _otherUserCommentRow(entry));
}
 
Widget _currentUserCommentRow(ChatEntry entry) {
  return Row(children: <Widget>[
    Container(child: _avatarLayout(entry)),
    SizedBox(
      width: 16.0,
    ),
    new Expanded(child: _messageLayout(entry, CrossAxisAlignment.start)),
  ]);
}
 
Widget _otherUserCommentRow(ChatEntry entry) {
  return Row(children: <Widget>[
    new Expanded(child: _messageLayout(entry, CrossAxisAlignment.end)),
    SizedBox(
      width: 16.0,
    ),
    Container(child: _avatarLayout(entry)),
  ]);
}
 
Widget _messageLayout(ChatEntry entry, CrossAxisAlignment alignment) {
  return Column(
    crossAxisAlignment: alignment,
    children: <Widget>[
      Text(entry.userName,
          style: TextStyle(fontSize: 14.0, color: Colors.grey)),
      Text(entry.message)
    ],
  );
}
 
Widget _avatarLayout(ChatEntry entry) {
  return CircleAvatar(
    backgroundImage: NetworkImage(entry.userImageUrl),
  );
}

メッセージのメールアドレスをみて、ログインしているユーザーと一緒であれば、自身が発信したメッセージとして左に寄せるレイアウトです。他者が発信したメッセージについては右側に寄せています。

最後に

ちょっと駆け足で解説しましたが、いかがだったでしょうか。実装は特にはまることもなく、順調に進みました。ブログを書いてる時間の方が圧倒的に長い。。。がんばろー。今回実装した、ソースコードはこちらに置いてあります。整理はしてないので汚いですが、誰かのお役に立てれば嬉しいです。

参考文献

  • Google APIs for Android: Authenticating Your Client
  • flutter/plugins
  • flutter-chat-app
  • いまのAWSパートナー、請求代行だけなんだよな