FlutterでFirebase Authenticationを使ってみた
大阪オフィスの山田です。通勤が唯一の運動になっていて運動不足が加速しています。今回は前回のチャットアプリに、Firebase Authenticationを使ってログイン機能をつけてみます。Firebase Authenticationを使うこと自体が初めてです。
今回やること
前回作ったチャットアプリは、誰が投稿したかわからない状態だったので、Firebase Authenticationを使い、ログイン機能を追加して誰の投稿かわかるようにします。ログインにはGoogleアカウントを使用します。こちらのリポジトリを部分的に参考にしています。
完成イメージ
開発環境
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.gitFramework • revision c7ea3ca377 (3 months ago) • 2018-05-29 21:07:33 +0200Engine • revision 1ed25ca7b7Tools • Dart 2.0.0-dev.58.0.flutter-f981f09760 |
準備
各プラットフォームの設定
Android、iOS、各プラットフォームのプロジェクトをFirebase上に作成し、google-service.json、GoogleService-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.plistのREVERSED_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_authとgoogle_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 | @overrideWidget 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), );} |
メッセージのメールアドレスをみて、ログインしているユーザーと一緒であれば、自身が発信したメッセージとして左に寄せるレイアウトです。他者が発信したメッセージについては右側に寄せています。
最後に
ちょっと駆け足で解説しましたが、いかがだったでしょうか。実装は特にはまることもなく、順調に進みました。ブログを書いてる時間の方が圧倒的に長い。。。がんばろー。今回実装した、ソースコードはこちらに置いてあります。整理はしてないので汚いですが、誰かのお役に立てれば嬉しいです。