Flutterで一般的な使い方でFirestoreにアクセスしようとすると、ドキュメント名やレスポンスを文字列でマッチングする必要があるため、型情報の復元のためのコードを色々書く必要がある。
Cloud Firestore ODMはその名の通りObject Document Mapperで、これを経由してFirestoreにアクセスすることで各種ドキュメントやフィールド情報を型安全に取得できる。今までStringのキーでアクセスするのが気持ち悪くて避けていたが、それが改善されそうなので使ってみた。
Cloud Firestore ODM は現在アルファ版です。今後大きな変更があるかもしれません。詳細はディスカッションを読むようアナウンスされています。
事前準備
まだFirebaseの準備ができていない場合は、ドキュメントをもとにflutterfireの設定する。
その後、アプリ起動時に初期化処理を呼び出す。
void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); runApp(MyApp()); }
cloud_firestoreのインストール
firestoreを導入する。iOSは追加の設定があるので注意
flutter pub add cloud_firestore flutter run
cloud_firestore_odmのインストール
cloud_firestore_odmと一緒に、依存するライブラリも導入する。
flutter pub add cloud_firestore_odm flutter pub add json_annotation flutter pub add --dev build_runner flutter pub add --dev cloud_firestore_odm_generator flutter pub add --dev json_serializable
モデル作成
JsonSerializable用の定義
const firestoreSerializable = JsonSerializable( converters: firestoreJsonConverters, // The following values could alternatively be set inside your `build.yaml` explicitToJson: true, createFieldMap: true, );
モデルクラスを作成する。読みやすさのためにreferenceに対する参照はモデルクラスの上部に記載したが、build_runnerに失敗する場合は一度コメントアウトして実行する。 なお、ドキュメントではidの宣言に関する実装をoptionalのように表現されているが、個人的にはすべてのモデルで宣言していいと思う。あって困るものではないし。
part 'user.g.dart'; @Collection<User>('users') final usersRef = UserCollectionReference(); @firestoreSerializable class User { User({ required this.name, required this.age, required this.email, String? id, // データ作成時はidを宣言しないので、optional引数にしている }) : id = id ?? ''; @Id() // この宣言があることで、ドキュメントのidをマッピングするコードが生成される。 final String id; final String name; final int age; final String email; }
定義が完了したら、build_runnerでファイル生成する。
flutter pub run build_runner build --delete-conflicting-outputs
データ生成
任意のタイミングでaddのメソッドを呼び出す。
final doc = await usersRef.add( User( name: "name1", age: 1, email: foobarbaz@yourdomain, ), );
データ呼び出し
ドキュメントではFirestoreBuilderでwidgerを生成する方法が紹介されている。
class User extends StatelessWidget { User(this.id); final String id; @override Widget build(BuildContext context) { return FirestoreBuilder<UserDocumentSnapshot>( // Access a specific document ref: usersRef.doc(id), builder: (context, AsyncSnapshot<UserDocumentSnapshot> snapshot, Widget? child) { if (snapshot.hasError) return Text('Something went wrong!'); if (!snapshot.hasData) return Text('Loading user...'); // Access the UserDocumentSnapshot UserDocumentSnapshot documentSnapshot = snapshot.requireData; if (!documentSnapshot.exists) { return Text('User does not exist.'); } User user = documentSnapshot.data!; return Text('User name: ${user.name}, age ${user.age}'); } ); } }
サブコレクションと合わせてデータを整形するなど、細かい操作をしたい場合はFutureで受け取ったデータを使って自分でWidgetを組み立てるほうが楽かもしれない。
usersRef.doc(widget.id!).get().then( (value) { setState(() { _user = value.data; }); }, );
まとめ
モデルのバリデーション方法やサブコレクションの連携方法も紹介されているので、気になる方はドキュメントを参照すること。2023/03/04時点で4ページしかなく、楽にキャッチアップできる。