FlutterでFirebase Authenticationを使ってみた
大阪オフィスの山田です。通勤が唯一の運動になっていて運動不足が加速しています。今回は前回のチャットアプリに、Firebase Authenticationを使ってログイン機能をつけてみます。Firebase Authenticationを使うこと自体が初めてです。
今回やること
前回作ったチャットアプリは、誰が投稿したかわからない状態だったので、Firebase Authenticationを使い、ログイン機能を追加して誰の投稿かわかるようにします。ログインにはGoogleアカウントを使用します。こちらのリポジトリを部分的に参考にしています。
完成イメージ
開発環境
flutter doctor
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
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.json
、GoogleService-Info.plist
をプロジェクトに追加します。こちらの記事の「準備」の章で各プラットフォームで設定をしていますのでご参照ください。
Firebase Authenticationの準備
最初にFirebase Authenticationを使えるようにします。Firebase Consoleにログインして、左のペインからAuthenicationを選択します。
Authenticationの「ログイン方法」をクリックしてGoogle認証を使用できるようにします。
プロジェクトのサポートメールが必須なので、メールアドレスを入力します。
AndroidでGoogleアカウント認証を行う場合は、アプリのSHA-1フィンガープリントを設定する必要があります。ターミナルで以下のコマンドを実行して、SHA-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
を抜き出して設定してください。
<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時点のバージョンを使用しています)
dependencies: firebase_auth: ^0.5.19 google_sign_in: ^3.0.4
ソースコードにはこちらを記述してimportします、
import 'package:firebase_auth/firebase_auth.dart'; import 'package:google_sign_in/google_sign_in.dart';
Googleアカウントでログインできるボタンを用意
まず、クラス変数として、現在ログインしているユーザー
を定義します。
FirebaseUser _user;
ログインしなければ、ログインボタンを表示して、ログインしていればチャットを表示します。
@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
こちらを参考にログイン処理を記述します。
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を持ちます。
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, }; } }
チャット画面のレイアウト
少し長いですが、実装です。
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), ); }
メッセージのメールアドレスをみて、ログインしているユーザーと一緒であれば、自身が発信したメッセージとして左に寄せるレイアウトです。他者が発信したメッセージについては右側に寄せています。
最後に
ちょっと駆け足で解説しましたが、いかがだったでしょうか。実装は特にはまることもなく、順調に進みました。ブログを書いてる時間の方が圧倒的に長い。。。がんばろー。今回実装した、ソースコードはこちらに置いてあります。整理はしてないので汚いですが、誰かのお役に立てれば嬉しいです。