この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
大阪オフィスの山田です。通勤が唯一の運動になっていて運動不足が加速しています。今回は前回のチャットアプリに、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),
);
}
メッセージのメールアドレスをみて、ログインしているユーザーと一緒であれば、自身が発信したメッセージとして左に寄せるレイアウトです。他者が発信したメッセージについては右側に寄せています。
最後に
ちょっと駆け足で解説しましたが、いかがだったでしょうか。実装は特にはまることもなく、順調に進みました。ブログを書いてる時間の方が圧倒的に長い。。。がんばろー。今回実装した、ソースコードはこちらに置いてあります。整理はしてないので汚いですが、誰かのお役に立てれば嬉しいです。