Cloud Firestore ODM
🔥Flutterで使える珍しいパッケージ
昔使ってみたことあったので、気になって使ってみた。
これ
公式サイトの情報はこれぐらいしかない?
add package:
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
🤔まだ使えるのか???
LIKES 107か...
公式の解説を翻訳すると...
モデルとは、Firestore上でどのようなデータを受信し、どのようなデータを変異させるかを表すものです。ODMは、すべてのデータがモデルに対して検証されることを保証し、モデルが有効でない場合はエラーがスローされます。
はじめに、Firestoreデータベース上に 「Users 」というコレクションがあるとします。このコレクションには、名前、年齢、電子メール(など)のようなユーザ情報を含む多くのドキュメントが含まれています。このデータのモデルを定義するために、クラスを作成します:
import 'package:json_annotation/json_annotation.dart';
import 'package:cloud_firestore_odm/cloud_firestore_odm.dart';
// This doesn't exist yet...! See "Next Steps"
part 'user.g.dart';
(explicitToJson: true)
class User {
User({
required this.name,
required this.age,
required this.email,
});
final String name;
final int age;
final String email;
}
🔨Creating references
モデル単体では何もしない。その代わりに、モデルを使って「参照」を作成します。参照によって、ODMはモデルを使用してFirestoreと対話できるようになります。
参照を作成するには、Firestoreデータベース内のコレクションへのポインタとして使用されるCollectionアノテーションを使用します。例えば、データベースのルートにあるusersコレクションは、以前に定義したUsersモデルに対応します:
(explicitToJson: true)
class User {
// ...
}
<User>('users')
final usersRef = UserCollectionReference();
🤔ここまでまとめると
モデルを作る。モデルは何もしてくれない💦
コードを自動生成して、「参照」を作成する。
そもそもこのパッケージの良いところは何かというと、TimestampConverterを作らなくても良い。freezed
の面倒臭いところをカバーしてくれる。ただし、Timestamp -> Datetime -> 日本時間に変換するのは自作する必要がある。intl
を使えばできる。
使ってみるか...
ダミーのデータをこんな感じで用意する。
import 'package:json_annotation/json_annotation.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:cloud_firestore_odm/cloud_firestore_odm.dart';
// これはまだ存在しない...!次のステップ "を参照
part 'example.g.dart';
/// カスタムJsonSerializableアノテーション。
/// TimestampsやDateTimesのようなオブジェクトのデコードをサポートするカスタムJsonSerializableアノテーション。
/// この変数は、異なるモデル間で再利用することができます。
const firestoreSerializable = JsonSerializable(
converters: firestoreJsonConverters,
// 以下の値を `build.yaml` 内で設定することもできる。
explicitToJson: true,
createFieldMap: true,
createPerFieldToJson: true,
);
class Example {
Example({
required this.title,
required this.createdAt,
});
final String title;
/// TimestampsやDateTimesのようなオブジェクトのデコードをサポートするカスタムJsonSerializableアノテーション。
()
Timestamp? createdAt;
}
// このコードは、次のコマンドを実行することで生成されます。
// flutter pub run build_runner watch --delete-conflicting-outputs
<Example>('example')
final examplesRef = ExampleCollectionReference();
自動生成のコマンドを実行する
flutter pub run build_runner watch --delete-conflicting-outputs
UIに、Cloud Firestoreから取得したデータを表示する
取得したデータを表示するには、FirestoreBuilder
となるものを使う。querySnapshotだから、一度だけ全てのデータを取得するようだ。
<User>('users')
final usersRef = UserCollectionReference();
// ...
class UsersList extends StatelessWidget {
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...');
// Access the QuerySnapshot
UserQuerySnapshot querySnapshot = snapshot.requireData;
return ListView.builder(
itemCount: querySnapshot.docs.length,
itemBuilder: (context, index) {
// Access the User instance
User user = querySnapshot.docs[index].data;
return Text('User name: ${user.name}, age ${user.age}');
},
);
}
);
}
}
main.dart
を編集して、こんな感じにすればデータを表示できます。
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:cloud_firestore_odm/cloud_firestore_odm.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:freezed_extention/example/example.dart';
import 'package:freezed_extention/firebase_options.dart';
import 'package:intl/intl.dart';
// yyyy-MM-dd HH:mm:ssで表示する拡張機能
// intlを使う
// extension type TimeConvert(Timestamp timestamp) {
// String toTime() {
// return DateFormat('yyyy-MM-dd HH:mm:ss').format(timestamp.toDate());
// }
// }
// 拡張機能の定義を修正
extension TimeConvert on Timestamp {
String toTime() {
return DateFormat('yyyy-MM-dd HH:mm:ss').format(toDate());
}
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}
<Example>('example')
final examplesRef = ExampleCollectionReference();
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Firebase Example'),
),
body: FirestoreBuilder<ExampleQuerySnapshot>(
ref: examplesRef,
builder: (context, AsyncSnapshot<ExampleQuerySnapshot> snapshot, Widget? child) {
if (snapshot.hasError) return Text('Something went wrong!');
if (!snapshot.hasData) return Text('Loading users...');
// Access the QuerySnapshot
ExampleQuerySnapshot querySnapshot = snapshot.requireData;
return ListView.builder(
itemCount: querySnapshot.docs.length,
itemBuilder: (context, index) {
// Access the User instance
Example example = querySnapshot.docs[index].data;
return Text('title: ${example.title}, time ${example.createdAt!.toTime()}');
},
);
}
),
);
}
}
こんな感じでございます。
感想
なかなか、面白い機能だが、riverpod
のプロバイダーが使えなさそう。いい感じで使い分ければ便利なものになるかもしれない???
Discussion