author: 針猫 summary: この Codelab では、FlutterとFirebase及びその関連パッケージの使い方をToDoアプリ制作を通して学びます。 id: todo-app categories: flutter environments: Android status: Draft feedback link: https://github.com/gdsc-osaka/codelab-flutter-tutorial-todo/issues analytics account: 198538794
この Codelab では、次のような Todo アプリの作成を通して Firebase の基本的な使い方を学びます。
Google アカウントで Firebase にログインし、新しいプロジェクトを作成します。
Firebase にログインし、プロジェクトの作成をクリック
プロジェクト名とプロジェクトIDを設定
アナリティクスの地域を日本に設定し、✓ を押下
ここ から Todo アプリのテンプレートをダウンロードします。
ダウンロードしたZIPファイルを任意の場所に展開し、エディタまたは IDE でルートディレクトリを開きます。
なお、アプリのソースコードは GitHub で公開しています。
Flutter アプリに Firebase を追加する を参照してFirebaseをインストールします。
Riverpod とは、Flutter 内で簡単に状態(State)を管理できるパッケージです。この Todo アプリでは、Riverpod を使用して Firebase などのデータを管理します。
以下のコマンドを実行して、アプリに Riverpod を追加します。
flutter pub add riverpod
flutter pub get
Firebase Auth から得られるユーザーデータを Riverpod で管理してみましょう。
/lib/api/auth_providers.dart
に以下のコードを追加します。
final userChangesProvider = StreamProvider<User?>((ref) => FirebaseAuth.instance.userChanges());
Firebase Authentication を使えば、メールアドレス/パスワード認証・電話番号認証・Google認証・各SNSの認証をクライアント(アプリ)のプログラムを書くだけで実装できます。
flutter pub add firebase_auth
flutter pub get
Firebase Console から、以下の手順の通りに認証を有効化します。
Authentication を有効化
メール認証を有効化
Google認証を有効化
次のセクションでは、アプリ内にユーザー認証を実装します。
このセクションでは、Flutter アプリにメールアドレス認証を追加します。
/lib/features/auth/email_login_page.dart
を開き、36行目の signIn()
にメールアドレス認証を追加します。
signIn() async {
if (_formKey.currentState?.validate() ?? false) {
// ...
// Firebase Authを追加
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: _email,
password: _password,
);
}
}
次に、ログインに失敗したときの例外を追加します。
なお、ログインやユーザー登録など、実行する関数によってエラーコード (e.code
) が異なります。
対応するエラーコードについては、[Flutter×Firebase] firebase_authのエラーハンドリング(例外処理、try-catch)を学ぶ などを参照してください。
signIn() async {
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: _email,
password: _password,
);
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found' || e.code == 'wrong-password') {
// スナックバーを表示
messenger.showSnackBar(const SnackBar(content: Text('ユーザー名またはパスワードが不明です')));
}
} catch (e) {
// デバッグ時のみエラーコードを出力
if (kDebugMode) {
print(e);
}
}
}
認証ボタンが押下されたときに Loading のオーバーレイが表示されるコードを追加します。
なお、以降で省略できる個所は ...
と記述します。
signIn() async {
if (...) {
// LoaderOverlayを追加
context.loaderOverlay.show();
final messenger = ScaffoldMessenger.of(context);
try {
// ...
if (mounted) {
context.loaderOverlay.hide();
context.go(HomePage.name);
}
} on FirebaseAuthException catch (e) {
context.loaderOverlay.hide();
// ...
} catch (e) {
// ...
}
}
}
このセクションでは、Todo アプリにメールアドレスのユーザー登録を追加します。
/lib/features/auth/email_signup_page.dart
を開き、32行目の signUp()
にユーザー登録のコードを追加します。
signUp() async {
if (...) {
final credential = await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: _email,
password: _password,
);
}
}
前のセクションと同様に、ユーザー登録に失敗したときの例外処理を追加します。
signUp() async {
if (...) {
try {
final credential = await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: _email,
password: _password,
);
} on FirebaseAuthException catch (e) {
if (e.code == 'weak-password') {
messenger.showSnackBar(const SnackBar(content: Text('より複雑なパスワードを使用してください')));
} else if (e.code == 'email-already-in-use') {
messenger.showSnackBar(const SnackBar(content: Text('メールアドレスが既に使用されています')));
}
} catch (e) {
if (kDebugMode) {
print(e);
}
}
}
}
signIn() async {
if (...) {
// LoaderOverlayを追加
context.loaderOverlay.show();
final messenger = ScaffoldMessenger.of(context);
try {
// ...
if (mounted) {
context.loaderOverlay.hide();
context.go(HomePage.name);
}
} on FirebaseAuthException catch (e) {
context.loaderOverlay.hide();
// ...
} catch (e) {
// ...
}
}
}
ターミナルまたはコマンドプロンプトを起動し、Todo アプリのルートディレクトリに移動します。以下のコマンドを実行します。
cd .\android\
./gradlew signingReport
> Task :google_sign_in_android:signingReport
で出力される SHA-1 と SHA-256 をメモしておきます。
このセクションでは、ユーザーのデータを Firestore に保存します。
/lib/api/firestore_api.dart
を開き、以下のコードを追加します。
このコードでは、データベースの users コレクションにユーザーの UID (User ID) を ID としたデータをセットしています。
final _db = FirebaseFirestore.instance;
final _users = _db.collection('users');
class FirestoreAPI {
Future<void> addUser(User user) async {
final userData = DBUser.map();
await _users.doc(user.uid).set(userData);
existsDBUser = true;
}
}
次に、Firebase Auth の実装で使用した /lib/features/auth/email_signup_page.dart
を開き、以下のコードを追加します。
signUp() async {
if (...) {
// ...
try {
final credential = await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: _email,
password: _password,
);
final user = credential.user;
if (user != null) {
FirestoreAPI.instance.addUser(user);
}
// ...
} on FirebaseAuthException catch (e) {
// ...
}
}
}
これにより、新しくユーザー登録されたときに Firestore にもデータが追加されるようになりました。
次のセクションでは、Todo のタスクを追加する方法を学びます。
このセクションでは、タスクのCRUD(Create, Read, Update, Delete) の方法を学びます。
以下のプロバイダを作成し、全てのタスクの読み取りを実装します。
final allTaskProvider = StreamProvider.autoDispose<List<Task>>((ref) {
return ref.watch(userProvider).when(
data: (user) {
if (user == null) {
return const Stream.empty();
} else {
final uid = user.uid;
final ref = _db.collection('users').doc(uid).collection('tasks');
final snapshots = ref.snapshots();
return snapshots.map((snapshot) {
final tasks = snapshot.docs
.map((doc) {
final data = doc.data();
if (data.containsKey('updatedAt') && data['updatedAt'] == null) {
data['updatedAt'] = Timestamp.fromDate(DateTime.now());
}
return Task.fromJson(data);
}).toList();
return tasks;
});
}
},
error: (err, stack) => const Stream.empty(),
loading: () => const Stream.empty());
});
次に、特定のタスクを取得するプロバイダを作成します。
final taskProvider = StreamProvider.autoDispose.family<Task, String>((ref, taskId) {
final user = _auth.currentUser;
if (user == null) {
return const Stream.empty();
} else {
final uid = user.uid;
final ref = _db.collection('users').doc(uid).collection('tasks').doc(taskId);
final data = ref.snapshots().map((event) {
final data = event.data();
if (data == null) {
return null;
}
if (data.containsKey('updatedAt') && data['updatedAt'] == null) {
data['updatedAt'] = Timestamp.fromDate(DateTime.now());
}
return Task.fromJson(data);
}).where((task) => task != null).cast<Task>();
return data;
}
});