2021-12-15 (水) by Kosuke Saigusa
Flutter 大学共同勉強会
今日はサンプルのソースコードや参考文献を一緒に眺めながら進める形式を中心に行うので、いつも通り(いつも以上に!)Slack やお喋りをしながらわいわいやりましょう!🙌
2021-12-15 (水) by Kosuke Saigusa
Flutter 大学共同勉強会
今日はサンプルのソースコードや参考文献を一緒に眺めながら進める形式を中心に行うので、いつも通り(いつも以上に!)Slack やお喋りをしながらわいわいやりましょう!🙌
flutter pub add cloud_firestore_odm flutter pub add json_annotation
flutter pub add cloud_firestore_odm flutter pub add json_annotation
FlutterFire (Flutter x Cloud Firestore) のアプリ開発を 型安全 に開発していくためのパッケージ。
Firestore Document ↔ Dart クラスのコードの自動生成ができ、Object/Document Mapper (ODM) として機能する。
json_serializable や freezed と withConverter
を FutureBuilder
や StreamBuilder
組み合わせて JSON (Document) 色付け(Flutter のウィジェットとして描画)していたのを、型安全に、より少ないコードでまとめて行えるようになることを目指しているイメージか。
さらに、対応する CollectionReference
や DocumentReference
にも、Firestore のパスに紐付いた形で 型が付いたコードも自動生成されるので快適!
とはいえ、アルファ版ということでまだまだ不具合や未実装(実装予定)の機能は多くあるみたい。
私たちがやるのは、Firestore の Document と対応するデータクラス風の定義をこんなふうに書いて、
import 'package:json_annotation/json_annotation.dart'; import 'package:cloud_firestore_odm/cloud_firestore_odm.dart'; part 'user.g.dart'; @JsonSerializable() class User { User({ required this.name, required this.age, required this.email, }); final String name; final int age; final String email; } @Collection<User>('users') final usersRef = UserCollectionReference();
import 'package:json_annotation/json_annotation.dart'; import 'package:cloud_firestore_odm/cloud_firestore_odm.dart'; part 'user.g.dart'; @JsonSerializable() class User { User({ required this.name, required this.age, required this.email, }); final String name; final int age; final String email; } @Collection<User>('users') final usersRef = UserCollectionReference();
自動生成コマンドを実行するかんたんなお仕事。
flutter pub run build_runner build --delete-conflicting-outputs
flutter pub run build_runner build --delete-conflicting-outputs
生成されたコードは後で一緒に見てみましょう。
@Collection<User>('users') final usersRef = UserCollectionReference(); class UsersList extends StatelessWidget { @override Widget build(BuildContext context) { return FirestoreBuilder<UserQuerySnapshot>( ref: usersRef, builder: (context, AsyncSnapshot<UserQuerySnapshot> snapshot, Widget? child) { if (snapshot.hasError) return Text('Something went wrong!'); if (!snapshot.hasData) return Text('Loading users...'); UserQuerySnapshot querySnapshot = snapshot.requireData; return ListView.builder( itemCount: querySnapshot.docs.length, itemBuilder: (context, index) { User user = querySnapshot.docs[index].data; return Text('User name: ${user.name}, age ${user.age}'); }, ); } ); } }
@Collection<User>('users') final usersRef = UserCollectionReference(); class UsersList extends StatelessWidget { @override Widget build(BuildContext context) { return FirestoreBuilder<UserQuerySnapshot>( ref: usersRef, builder: (context, AsyncSnapshot<UserQuerySnapshot> snapshot, Widget? child) { if (snapshot.hasError) return Text('Something went wrong!'); if (!snapshot.hasData) return Text('Loading users...'); UserQuerySnapshot querySnapshot = snapshot.requireData; return ListView.builder( itemCount: querySnapshot.docs.length, itemBuilder: (context, index) { User user = querySnapshot.docs[index].data; return Text('User name: ${user.name}, age ${user.age}'); }, ); } ); } }
withConverter
を FutureBuilder
や StreamBuilder
が全部一緒になって、Firestore の CollectionReference, DocumentReference に勝手に型をつけてくれる。モデリングの一例を docs/scheme.jsonc に置いています(※これがベストの例・唯一の正解というわけではありません)。
だけがあるかんたんなチャットアプリを題材にしています。
Firestore ODM の活用例や自動生成されたコードも含めて、全体を軽く一緒に読んでみましょう。
TypeScript で書く Cloud Functions でも、できる限り型安全に読み書きしたいので、
tsconfig.json
をよしなに設定しつつ、types
ディレクトリ下に *.d.ts
のような型定義ファイルをつくります。
今回はそこに interface
を定義することにしました。
interface AppUser { reference?: FirebaseFirestore.DocumentReference | null; createdAt?: FirebaseFirestore.Timestamp | null; updatedAt?: FirebaseFirestore.Timestamp | null; name: string; imageURL: string | null; }
interface AppUser { reference?: FirebaseFirestore.DocumentReference | null; createdAt?: FirebaseFirestore.Timestamp | null; updatedAt?: FirebaseFirestore.Timestamp | null; name: string; imageURL: string | null; }
withConverter
で使用するための converter も対応する分だけ作っておくと気持ちよくなれます。
import { FieldValue } from '@google-cloud/firestore' export const appUserConverter = { fromFirestore(qds: FirebaseFirestore.QueryDocumentSnapshot): AppUser { const data = qds.data() return { reference: qds.ref, createdAt: data.createdAt ?? null, updatedAt: data.updatedAt ?? null, name: data.name, imageURL: data.imageURL ?? null, } }, toFirestore(obj: AppUser): FirebaseFirestore.DocumentData { return { createdAt: FieldValue.serverTimestamp(), updatedAt: FieldValue.serverTimestamp(), name: obj.name, imageURL: obj.imageURL, } } }
import { FieldValue } from '@google-cloud/firestore' export const appUserConverter = { fromFirestore(qds: FirebaseFirestore.QueryDocumentSnapshot): AppUser { const data = qds.data() return { reference: qds.ref, createdAt: data.createdAt ?? null, updatedAt: data.updatedAt ?? null, name: data.name, imageURL: data.imageURL ?? null, } }, toFirestore(obj: AppUser): FirebaseFirestore.DocumentData { return { createdAt: FieldValue.serverTimestamp(), updatedAt: FieldValue.serverTimestamp(), name: obj.name, imageURL: obj.imageURL, } } }
これを runApp
の前に実行しておきます。dart-define
などでローカルエミュレータに接続するフラグを受け取っておくと良いです。
Future<void> setUpLocalEmulator() async { const localhost = 'localhost'; FirebaseFirestore.instance.settings = Settings( host: Platform.isAndroid ? '10.0.2.2:8080' : 'localhost:8080', sslEnabled: false, persistenceEnabled: true, ); FirebaseFirestore.instance.useFirestoreEmulator(localhost, 8080); FirebaseFunctions.instance.useFunctionsEmulator(localhost, 5001); FirebaseStorage.instanceFor(bucket: 'default-bucket'); await Future.wait( [ FirebaseAuth.instance.useAuthEmulator(localhost, 9099), FirebaseStorage.instance.useStorageEmulator(localhost, 9199), ], ); }
Future<void> setUpLocalEmulator() async { const localhost = 'localhost'; FirebaseFirestore.instance.settings = Settings( host: Platform.isAndroid ? '10.0.2.2:8080' : 'localhost:8080', sslEnabled: false, persistenceEnabled: true, ); FirebaseFirestore.instance.useFirestoreEmulator(localhost, 8080); FirebaseFunctions.instance.useFunctionsEmulator(localhost, 5001); FirebaseStorage.instanceFor(bucket: 'default-bucket'); await Future.wait( [ FirebaseAuth.instance.useAuthEmulator(localhost, 9099), FirebaseStorage.instance.useStorageEmulator(localhost, 9199), ], ); }
デモ的にいろいろなコードを見てみましょう!!