SlideShare a Scribd company logo
1 of 17
Download to read offline
GDG DevFest Tokyo 2023
2023/12/09
Cloud Firestore のスキーマ定義の
Flutter 向けコード生成について
@kosukesaigusa
株式会社SODA
○ 2023年8月〜
○ Flutter エンジニア
○ 趣味としても Flutter, Dart の OSS の開発など
⇧⇧⇧
atama plus株式会社
○ Web エンジニア
○ Django, Angular, React など
Kosuke Saigusa- @kosukesaigusa
@kosukesaigusa
Flutter x Cloud Firestore で
Todo アプリを作ろうとすると...
ちょっとまじめに
仕様
Todo 一覧を一括取得またはリアルタイム更新を取得できる
● タイトル、完了済みかどうか、作成日時、更新日時をもつ
● fromJson 時にドキュメント ID も取り出してインスタンスに含める
Todo を作成できる
● 作成日時は自動で FieldValue.serverTimestamp() が適用される
Todo を更新できる
● 指定したフィールドだけを更新できる(例:完了済みかどうかフィールドのみ)
● 作成日時・更新日時は自動で FieldValue.serverTimestamp() が適用される
読み書き処理はすべて型安全に行う
● withConverter を適用する
class ReadTodo {
const ReadTodo({
required this.todoId,
required this.title,
required this.isCompleted,
required this.createdAt,
required this.updatedAt,
});
final String todoId;
final String title;
final bool isCompleted;
final DateTime? createdAt;
final DateTime? updatedAt;
}
factory ReadTodo.fromDocumentSnapshot(DocumentSnapshot ds) {
final data = ds.data()! as Map<String, dynamic>;
return ReadTodo.fromJson(<String, dynamic>{
'todoId': ds.id,
...data,
});
}
factory ReadTodo.fromJson(Map<String, dynamic> json) {
return ReadTodo(
todoId: json['todoId'] as String,
title: json['title'] as String,
isCompleted: json['isCompleted'] as bool? ?? false,
createdAt: (json['createdAt'] as Timestamp?)?.toDate(),
updatedAt: (json['updatedAt'] as Timestamp?)?.toDate(),
);
}
DocumentSnapshot からドキュメント ID を取得してインスタンスに含める
class ReadTodo {
const ReadTodo({
required this.todoId,
required this.title,
required this.isCompleted,
required this.createdAt,
required this.updatedAt,
});
final String todoId;
final String title;
final bool isCompleted;
final DateTime? createdAt;
final DateTime? updatedAt;
}
factory ReadTodo.fromDocumentSnapshot(DocumentSnapshot ds) {
final data = ds.data()! as Map<String, dynamic>;
return ReadTodo.fromJson(<String, dynamic>{
'todoId': ds.id,
...data,
});
}
factory ReadTodo.fromJson(Map<String, dynamic> json) {
return ReadTodo(
todoId: json['todoId'] as String,
title: json['title'] as String,
isCompleted: json['isCompleted'] as bool? ?? false,
createdAt: (json['createdAt'] as Timestamp?)?.toDate(),
updatedAt: (json['updatedAt'] as Timestamp?)?.toDate(),
);
}
isCompleted の読み取り時のデフォルト値として false を与える
class ReadTodo {
const ReadTodo({
required this.todoId,
required this.title,
required this.isCompleted,
required this.createdAt,
required this.updatedAt,
});
final String todoId;
final String title;
final bool isCompleted;
final DateTime? createdAt;
final DateTime? updatedAt;
}
factory ReadTodo.fromDocumentSnapshot(DocumentSnapshot ds) {
final data = ds.data()! as Map<String, dynamic>;
return ReadTodo.fromJson(<String, dynamic>{
'todoId': ds.id,
...data,
});
}
factory ReadTodo.fromJson(Map<String, dynamic> json) {
return ReadTodo(
todoId: json['todoId'] as String,
title: json['title'] as String,
isCompleted: json['isCompleted'] as bool? ?? false,
createdAt: (json['createdAt'] as Timestamp?)?.toDate(),
updatedAt: (json['updatedAt'] as Timestamp?)?.toDate(),
);
}
Cloud Firestore の Timestamp を Dart の DateTime に変換する
Future<List<ReadTodo>> fetchTodos() async {
final querySnapshot = await FirebaseFirestore.instance
.collection('todos')
.withConverter<ReadTodo>(
fromFirestore: (ds, _) => ReadTodo.fromDocumentSnapshot(ds),
toFirestore: (_, __) => throw UnimplementedError(),
)
.get();
return querySnapshot.docs
.map((queryDocumentSnapshot) => queryDocumentSnapshot.data())
.toList();
}
withConverter を用いて型安全に一括取得する
Stream<List<ReadTodo>> subscribeTodos() {
final streamQuerySnapshot = FirebaseFirestore.instance
.collection('todos')
.withConverter<ReadTodo>(
fromFirestore: (ds, _) => ReadTodo.fromDocumentSnapshot(ds),
toFirestore: (_, __) => throw UnimplementedError(),
)
.snapshots();
return streamQuerySnapshot.map(
(querySnapshot) => querySnapshot.docs
.map((queryDocumentSnapshot) => queryDocumentSnapshot.data())
.toList(),
);
}
withConverter を用いて型安全にリアルタイム更新を取得する
class CreateTodo {
const CreateTodo({
required this.title,
this.isCompleted,
});
final String title;
final bool? isCompleted;
Map<String, dynamic> toJson() {
return <String, dynamic>{
'title': title,
'isCompleted': isCompleted ?? false,
'createdAt': FieldValue.serverTimestamp(),
'updatedAt': FieldValue.serverTimestamp(),
};
}
}
Future<DocumentReference<CreateTodo>> createTodo({
required String title,
bool? isCompleted,
}) {
return FirebaseFirestore.instance
.collection('todos')
.withConverter<CreateTodo>(
fromFirestore: (_, __) => throw UnimplementedError(),
toFirestore: (obj, _) => obj.toJson(),
)
.add(CreateTodo(title: title, isCompleted: isCompleted));
}
title は必須、isCompleted は任意かつデフォルト値 false で作成する
class CreateTodo {
const CreateTodo({
required this.title,
this.isCompleted,
});
final String title;
final bool? isCompleted;
Map<String, dynamic> toJson() {
return <String, dynamic>{
'title': title,
'isCompleted': isCompleted ?? false,
'createdAt': FieldValue.serverTimestamp(),
'updatedAt': FieldValue.serverTimestamp(),
};
}
}
Future<DocumentReference<CreateTodo>> createTodo({
required String title,
bool? isCompleted,
}) {
return FirebaseFirestore.instance
.collection('todos')
.withConverter<CreateTodo>(
fromFirestore: (_, __) => throw UnimplementedError(),
toFirestore: (obj, _) => obj.toJson(),
)
.add(CreateTodo(title: title, isCompleted: isCompleted));
}
createdAt, updatedAt には自動で FieldValue.serverTimestamp() を適用する
Future<void> updateTodo({
required String todoId,
String? title,
bool? isCompleted,
DateTime? createdAt,
}) {
return FirebaseFirestore.instance
.collection('todos')
.doc(todoId)
.update(<String, dynamic>{
if (title != null) 'title': title,
if (isCompleted != null) 'isCompleted': isCompleted,
if (createdAt != null) 'createdAt': Timestamp.fromDate(createdAt),
'updatedAt': FieldValue.serverTimestamp(),
});
}
すべて任意で、非 null な値が与えられたフィールドのみを更新する
Future<void> updateTodo({
required String todoId,
String? title,
bool? isCompleted,
DateTime? createdAt,
}) {
return FirebaseFirestore.instance
.collection('todos')
.doc(todoId)
.update(<String, dynamic>{
if (title != null) 'title': title,
if (isCompleted != null) 'isCompleted': isCompleted,
if (createdAt != null) 'createdAt': Timestamp.fromDate(createdAt),
'updatedAt': FieldValue.serverTimestamp(),
});
}
updatedAt には自動で FieldValue.serverTimestamp() を適用する
@FirestoreDocument(path: 'todos', documentName: 'todo')
class Todo {
const Todo({
required this.title,
required this.isCompleted,
required this.createdAt,
required this.updatedAt,
});
final String title;
@ReadDefault(false)
@CreateDefault(false)
final bool isCompleted;
@AlwaysUseFieldValueServerTimestampWhenCreating()
final DateTime? createdAt;
@AlwaysUseFieldValueServerTimestampWhenCreating()
@AlwaysUseFieldValueServerTimestampWhenUpdating()
final DateTime? updatedAt;
}
これまでに取り上げたコードがすべて build_runner により自動で生成できる!
その他にも...
ネストされたサブコレクションのドキュメントのスキーマ定義にも対応
取得時のクエリ条件(where 句や orderBy 句など)も通常通り適用可能
FieldValue の指定も可能対応
● FieldValue.increment(), arrayUnion(), arrayRemove() など
基本的な読み書きメソッドも一通り自動生成
● 指定した 1 件を取得
● 指定した 1 件を購読
● 複数件を取得
● 複数件を購読
● ドキュメントの作成 (add, set)
● ドキュメントの更新 (update)
● ドキュメントの削除 (delete)
...など
@riverpod
TodoQuery todoQuery(TodoQueryRef _) => TodoQuery();
@riverpod
class TodoList extends _$TodoList {
@override
Future<List<ReadTodo>> build() =>
ref.watch(todoQueryProvider).fetchDocuments();
Future<DocumentReference<CreateTodo>> addTodo(String title) {
return ref
.read(todoQueryProvider)
.add(createTodo: CreateTodo(title: title));
}
Future<void> updateCompletionStatus({
required String todoId,
required bool isCompleted,
}) {
return ref.read(todoQueryProvider).update(
todoId: todoId,
updateTodo: UpdateTodo(isCompleted: isCompleted),
);
}
}
@FirestoreDocument(path: 'todos', documentName: 'todo')
class Todo {
const Todo({
required this.title,
required this.isCompleted,
required this.createdAt,
required this.updatedAt,
});
final String title;
@ReadDefault(false)
@CreateDefault(false)
final bool isCompleted;
@AlwaysUseFieldValueServerTimestampWhenCreating()
final DateTime? createdAt;
@AlwaysUseFieldValueServerTimestampWhenCreating()
@AlwaysUseFieldValueServerTimestampWhenUpdating()
final DateTime? updatedAt;
}
例:riverpod_generator と組み合わせてみると、こんなに簡潔にすべてを書ける!
flutterfire_gen: Cloud Firestore のスキーマ定義の Flutter 向けコード生成パッケージ
kosukesaigusa/flutterfire_gen
鋭意開発中です!(機能自体はほぼできています)
ユニットテストやドキュメントを整備して近日中のリリースを目指し
ています。
一緒に作ってみたい方、完成したらフィードバックをくれる方、使っ
てみたい方...お知らせください or 続報をお待ちください!
https://github.com/kosukesaigusa/flutterfire_gen
GDG DevFest Tokyo 2023
2023/12/09
Cloud Firestore のスキーマ定義の
Flutter 向けコード生成について
@kosukesaigusa

More Related Content

Similar to cloud_firestore_schema_code_generation_for_flutter.pdf

ASP.NETを利用したAJAX開発の応用
ASP.NETを利用したAJAX開発の応用ASP.NETを利用したAJAX開発の応用
ASP.NETを利用したAJAX開発の応用Sho Okada
 
第2回デザインパターン資料
第2回デザインパターン資料第2回デザインパターン資料
第2回デザインパターン資料gaaupp
 
JSがちょい好きになるプレゼン
JSがちょい好きになるプレゼンJSがちょい好きになるプレゼン
JSがちょい好きになるプレゼンJames Kirk
 
Spark Streaming Snippets
Spark Streaming SnippetsSpark Streaming Snippets
Spark Streaming SnippetsKoji Agawa
 
GoF デザインパターン 2009
GoF デザインパターン 2009GoF デザインパターン 2009
GoF デザインパターン 2009miwarin
 
OpenGLプログラミング
OpenGLプログラミングOpenGLプログラミング
OpenGLプログラミング幸雄 村上
 
Grafana Dashboards as Code
Grafana Dashboards as CodeGrafana Dashboards as Code
Grafana Dashboards as CodeTakuhiro Yoshida
 
PostgreSQL - C言語によるユーザ定義関数の作り方
PostgreSQL - C言語によるユーザ定義関数の作り方PostgreSQL - C言語によるユーザ定義関数の作り方
PostgreSQL - C言語によるユーザ定義関数の作り方Satoshi Nagayasu
 
Let's begin WebRTC
Let's begin WebRTCLet's begin WebRTC
Let's begin WebRTCyoshikawa_t
 
勉強会force#4 Chatter Integration
勉強会force#4 Chatter Integration勉強会force#4 Chatter Integration
勉強会force#4 Chatter IntegrationKazuki Nakajima
 
Real world android akka
Real world android akkaReal world android akka
Real world android akkaTaisuke Oe
 
Xamarinで作る 「オリジナルタイル地図」アプリ
Xamarinで作る「オリジナルタイル地図」アプリXamarinで作る「オリジナルタイル地図」アプリ
Xamarinで作る 「オリジナルタイル地図」アプリKohei Otsuka
 
C#勉強会 ~ C#9の新機能 ~
C#勉強会 ~ C#9の新機能 ~C#勉強会 ~ C#9の新機能 ~
C#勉強会 ~ C#9の新機能 ~Fujio Kojima
 
FxUG in Toyama - ASphalt2 container -
FxUG in Toyama - ASphalt2 container -FxUG in Toyama - ASphalt2 container -
FxUG in Toyama - ASphalt2 container -Akio Katayama
 
FxUG in Toyama - ASphalt2 container -
FxUG in Toyama - ASphalt2 container -FxUG in Toyama - ASphalt2 container -
FxUG in Toyama - ASphalt2 container -Akio Katayama
 
WPD-Fes #3 2015年のサバイバル学習術 Web開発技術の税引後利益 を最大化しよう!
WPD-Fes #3 2015年のサバイバル学習術 Web開発技術の税引後利益 を最大化しよう!WPD-Fes #3 2015年のサバイバル学習術 Web開発技術の税引後利益 を最大化しよう!
WPD-Fes #3 2015年のサバイバル学習術 Web開発技術の税引後利益 を最大化しよう!文樹 高橋
 
Infrastructure as code for azure
Infrastructure as code for azureInfrastructure as code for azure
Infrastructure as code for azureKeiji Kamebuchi
 
ソーシャルアプリ勉強会(第一回資料)配布用
ソーシャルアプリ勉強会(第一回資料)配布用ソーシャルアプリ勉強会(第一回資料)配布用
ソーシャルアプリ勉強会(第一回資料)配布用Yatabe Terumasa
 

Similar to cloud_firestore_schema_code_generation_for_flutter.pdf (20)

Sc2009autumn s2robot
Sc2009autumn s2robotSc2009autumn s2robot
Sc2009autumn s2robot
 
ASP.NETを利用したAJAX開発の応用
ASP.NETを利用したAJAX開発の応用ASP.NETを利用したAJAX開発の応用
ASP.NETを利用したAJAX開発の応用
 
第2回デザインパターン資料
第2回デザインパターン資料第2回デザインパターン資料
第2回デザインパターン資料
 
JSがちょい好きになるプレゼン
JSがちょい好きになるプレゼンJSがちょい好きになるプレゼン
JSがちょい好きになるプレゼン
 
Spark Streaming Snippets
Spark Streaming SnippetsSpark Streaming Snippets
Spark Streaming Snippets
 
GoF デザインパターン 2009
GoF デザインパターン 2009GoF デザインパターン 2009
GoF デザインパターン 2009
 
OpenGLプログラミング
OpenGLプログラミングOpenGLプログラミング
OpenGLプログラミング
 
Pfi Seminar 2010 1 7
Pfi Seminar 2010 1 7Pfi Seminar 2010 1 7
Pfi Seminar 2010 1 7
 
Grafana Dashboards as Code
Grafana Dashboards as CodeGrafana Dashboards as Code
Grafana Dashboards as Code
 
PostgreSQL - C言語によるユーザ定義関数の作り方
PostgreSQL - C言語によるユーザ定義関数の作り方PostgreSQL - C言語によるユーザ定義関数の作り方
PostgreSQL - C言語によるユーザ定義関数の作り方
 
Let's begin WebRTC
Let's begin WebRTCLet's begin WebRTC
Let's begin WebRTC
 
勉強会force#4 Chatter Integration
勉強会force#4 Chatter Integration勉強会force#4 Chatter Integration
勉強会force#4 Chatter Integration
 
Real world android akka
Real world android akkaReal world android akka
Real world android akka
 
Xamarinで作る 「オリジナルタイル地図」アプリ
Xamarinで作る「オリジナルタイル地図」アプリXamarinで作る「オリジナルタイル地図」アプリ
Xamarinで作る 「オリジナルタイル地図」アプリ
 
C#勉強会 ~ C#9の新機能 ~
C#勉強会 ~ C#9の新機能 ~C#勉強会 ~ C#9の新機能 ~
C#勉強会 ~ C#9の新機能 ~
 
FxUG in Toyama - ASphalt2 container -
FxUG in Toyama - ASphalt2 container -FxUG in Toyama - ASphalt2 container -
FxUG in Toyama - ASphalt2 container -
 
FxUG in Toyama - ASphalt2 container -
FxUG in Toyama - ASphalt2 container -FxUG in Toyama - ASphalt2 container -
FxUG in Toyama - ASphalt2 container -
 
WPD-Fes #3 2015年のサバイバル学習術 Web開発技術の税引後利益 を最大化しよう!
WPD-Fes #3 2015年のサバイバル学習術 Web開発技術の税引後利益 を最大化しよう!WPD-Fes #3 2015年のサバイバル学習術 Web開発技術の税引後利益 を最大化しよう!
WPD-Fes #3 2015年のサバイバル学習術 Web開発技術の税引後利益 を最大化しよう!
 
Infrastructure as code for azure
Infrastructure as code for azureInfrastructure as code for azure
Infrastructure as code for azure
 
ソーシャルアプリ勉強会(第一回資料)配布用
ソーシャルアプリ勉強会(第一回資料)配布用ソーシャルアプリ勉強会(第一回資料)配布用
ソーシャルアプリ勉強会(第一回資料)配布用
 

cloud_firestore_schema_code_generation_for_flutter.pdf

  • 1. GDG DevFest Tokyo 2023 2023/12/09 Cloud Firestore のスキーマ定義の Flutter 向けコード生成について @kosukesaigusa
  • 2. 株式会社SODA ○ 2023年8月〜 ○ Flutter エンジニア ○ 趣味としても Flutter, Dart の OSS の開発など ⇧⇧⇧ atama plus株式会社 ○ Web エンジニア ○ Django, Angular, React など Kosuke Saigusa- @kosukesaigusa @kosukesaigusa
  • 3. Flutter x Cloud Firestore で Todo アプリを作ろうとすると... ちょっとまじめに
  • 4. 仕様 Todo 一覧を一括取得またはリアルタイム更新を取得できる ● タイトル、完了済みかどうか、作成日時、更新日時をもつ ● fromJson 時にドキュメント ID も取り出してインスタンスに含める Todo を作成できる ● 作成日時は自動で FieldValue.serverTimestamp() が適用される Todo を更新できる ● 指定したフィールドだけを更新できる(例:完了済みかどうかフィールドのみ) ● 作成日時・更新日時は自動で FieldValue.serverTimestamp() が適用される 読み書き処理はすべて型安全に行う ● withConverter を適用する
  • 5. class ReadTodo { const ReadTodo({ required this.todoId, required this.title, required this.isCompleted, required this.createdAt, required this.updatedAt, }); final String todoId; final String title; final bool isCompleted; final DateTime? createdAt; final DateTime? updatedAt; } factory ReadTodo.fromDocumentSnapshot(DocumentSnapshot ds) { final data = ds.data()! as Map<String, dynamic>; return ReadTodo.fromJson(<String, dynamic>{ 'todoId': ds.id, ...data, }); } factory ReadTodo.fromJson(Map<String, dynamic> json) { return ReadTodo( todoId: json['todoId'] as String, title: json['title'] as String, isCompleted: json['isCompleted'] as bool? ?? false, createdAt: (json['createdAt'] as Timestamp?)?.toDate(), updatedAt: (json['updatedAt'] as Timestamp?)?.toDate(), ); } DocumentSnapshot からドキュメント ID を取得してインスタンスに含める
  • 6. class ReadTodo { const ReadTodo({ required this.todoId, required this.title, required this.isCompleted, required this.createdAt, required this.updatedAt, }); final String todoId; final String title; final bool isCompleted; final DateTime? createdAt; final DateTime? updatedAt; } factory ReadTodo.fromDocumentSnapshot(DocumentSnapshot ds) { final data = ds.data()! as Map<String, dynamic>; return ReadTodo.fromJson(<String, dynamic>{ 'todoId': ds.id, ...data, }); } factory ReadTodo.fromJson(Map<String, dynamic> json) { return ReadTodo( todoId: json['todoId'] as String, title: json['title'] as String, isCompleted: json['isCompleted'] as bool? ?? false, createdAt: (json['createdAt'] as Timestamp?)?.toDate(), updatedAt: (json['updatedAt'] as Timestamp?)?.toDate(), ); } isCompleted の読み取り時のデフォルト値として false を与える
  • 7. class ReadTodo { const ReadTodo({ required this.todoId, required this.title, required this.isCompleted, required this.createdAt, required this.updatedAt, }); final String todoId; final String title; final bool isCompleted; final DateTime? createdAt; final DateTime? updatedAt; } factory ReadTodo.fromDocumentSnapshot(DocumentSnapshot ds) { final data = ds.data()! as Map<String, dynamic>; return ReadTodo.fromJson(<String, dynamic>{ 'todoId': ds.id, ...data, }); } factory ReadTodo.fromJson(Map<String, dynamic> json) { return ReadTodo( todoId: json['todoId'] as String, title: json['title'] as String, isCompleted: json['isCompleted'] as bool? ?? false, createdAt: (json['createdAt'] as Timestamp?)?.toDate(), updatedAt: (json['updatedAt'] as Timestamp?)?.toDate(), ); } Cloud Firestore の Timestamp を Dart の DateTime に変換する
  • 8. Future<List<ReadTodo>> fetchTodos() async { final querySnapshot = await FirebaseFirestore.instance .collection('todos') .withConverter<ReadTodo>( fromFirestore: (ds, _) => ReadTodo.fromDocumentSnapshot(ds), toFirestore: (_, __) => throw UnimplementedError(), ) .get(); return querySnapshot.docs .map((queryDocumentSnapshot) => queryDocumentSnapshot.data()) .toList(); } withConverter を用いて型安全に一括取得する
  • 9. Stream<List<ReadTodo>> subscribeTodos() { final streamQuerySnapshot = FirebaseFirestore.instance .collection('todos') .withConverter<ReadTodo>( fromFirestore: (ds, _) => ReadTodo.fromDocumentSnapshot(ds), toFirestore: (_, __) => throw UnimplementedError(), ) .snapshots(); return streamQuerySnapshot.map( (querySnapshot) => querySnapshot.docs .map((queryDocumentSnapshot) => queryDocumentSnapshot.data()) .toList(), ); } withConverter を用いて型安全にリアルタイム更新を取得する
  • 10. class CreateTodo { const CreateTodo({ required this.title, this.isCompleted, }); final String title; final bool? isCompleted; Map<String, dynamic> toJson() { return <String, dynamic>{ 'title': title, 'isCompleted': isCompleted ?? false, 'createdAt': FieldValue.serverTimestamp(), 'updatedAt': FieldValue.serverTimestamp(), }; } } Future<DocumentReference<CreateTodo>> createTodo({ required String title, bool? isCompleted, }) { return FirebaseFirestore.instance .collection('todos') .withConverter<CreateTodo>( fromFirestore: (_, __) => throw UnimplementedError(), toFirestore: (obj, _) => obj.toJson(), ) .add(CreateTodo(title: title, isCompleted: isCompleted)); } title は必須、isCompleted は任意かつデフォルト値 false で作成する
  • 11. class CreateTodo { const CreateTodo({ required this.title, this.isCompleted, }); final String title; final bool? isCompleted; Map<String, dynamic> toJson() { return <String, dynamic>{ 'title': title, 'isCompleted': isCompleted ?? false, 'createdAt': FieldValue.serverTimestamp(), 'updatedAt': FieldValue.serverTimestamp(), }; } } Future<DocumentReference<CreateTodo>> createTodo({ required String title, bool? isCompleted, }) { return FirebaseFirestore.instance .collection('todos') .withConverter<CreateTodo>( fromFirestore: (_, __) => throw UnimplementedError(), toFirestore: (obj, _) => obj.toJson(), ) .add(CreateTodo(title: title, isCompleted: isCompleted)); } createdAt, updatedAt には自動で FieldValue.serverTimestamp() を適用する
  • 12. Future<void> updateTodo({ required String todoId, String? title, bool? isCompleted, DateTime? createdAt, }) { return FirebaseFirestore.instance .collection('todos') .doc(todoId) .update(<String, dynamic>{ if (title != null) 'title': title, if (isCompleted != null) 'isCompleted': isCompleted, if (createdAt != null) 'createdAt': Timestamp.fromDate(createdAt), 'updatedAt': FieldValue.serverTimestamp(), }); } すべて任意で、非 null な値が与えられたフィールドのみを更新する
  • 13. Future<void> updateTodo({ required String todoId, String? title, bool? isCompleted, DateTime? createdAt, }) { return FirebaseFirestore.instance .collection('todos') .doc(todoId) .update(<String, dynamic>{ if (title != null) 'title': title, if (isCompleted != null) 'isCompleted': isCompleted, if (createdAt != null) 'createdAt': Timestamp.fromDate(createdAt), 'updatedAt': FieldValue.serverTimestamp(), }); } updatedAt には自動で FieldValue.serverTimestamp() を適用する
  • 14. @FirestoreDocument(path: 'todos', documentName: 'todo') class Todo { const Todo({ required this.title, required this.isCompleted, required this.createdAt, required this.updatedAt, }); final String title; @ReadDefault(false) @CreateDefault(false) final bool isCompleted; @AlwaysUseFieldValueServerTimestampWhenCreating() final DateTime? createdAt; @AlwaysUseFieldValueServerTimestampWhenCreating() @AlwaysUseFieldValueServerTimestampWhenUpdating() final DateTime? updatedAt; } これまでに取り上げたコードがすべて build_runner により自動で生成できる! その他にも... ネストされたサブコレクションのドキュメントのスキーマ定義にも対応 取得時のクエリ条件(where 句や orderBy 句など)も通常通り適用可能 FieldValue の指定も可能対応 ● FieldValue.increment(), arrayUnion(), arrayRemove() など 基本的な読み書きメソッドも一通り自動生成 ● 指定した 1 件を取得 ● 指定した 1 件を購読 ● 複数件を取得 ● 複数件を購読 ● ドキュメントの作成 (add, set) ● ドキュメントの更新 (update) ● ドキュメントの削除 (delete) ...など
  • 15. @riverpod TodoQuery todoQuery(TodoQueryRef _) => TodoQuery(); @riverpod class TodoList extends _$TodoList { @override Future<List<ReadTodo>> build() => ref.watch(todoQueryProvider).fetchDocuments(); Future<DocumentReference<CreateTodo>> addTodo(String title) { return ref .read(todoQueryProvider) .add(createTodo: CreateTodo(title: title)); } Future<void> updateCompletionStatus({ required String todoId, required bool isCompleted, }) { return ref.read(todoQueryProvider).update( todoId: todoId, updateTodo: UpdateTodo(isCompleted: isCompleted), ); } } @FirestoreDocument(path: 'todos', documentName: 'todo') class Todo { const Todo({ required this.title, required this.isCompleted, required this.createdAt, required this.updatedAt, }); final String title; @ReadDefault(false) @CreateDefault(false) final bool isCompleted; @AlwaysUseFieldValueServerTimestampWhenCreating() final DateTime? createdAt; @AlwaysUseFieldValueServerTimestampWhenCreating() @AlwaysUseFieldValueServerTimestampWhenUpdating() final DateTime? updatedAt; } 例:riverpod_generator と組み合わせてみると、こんなに簡潔にすべてを書ける!
  • 16. flutterfire_gen: Cloud Firestore のスキーマ定義の Flutter 向けコード生成パッケージ kosukesaigusa/flutterfire_gen 鋭意開発中です!(機能自体はほぼできています) ユニットテストやドキュメントを整備して近日中のリリースを目指し ています。 一緒に作ってみたい方、完成したらフィードバックをくれる方、使っ てみたい方...お知らせください or 続報をお待ちください! https://github.com/kosukesaigusa/flutterfire_gen
  • 17. GDG DevFest Tokyo 2023 2023/12/09 Cloud Firestore のスキーマ定義の Flutter 向けコード生成について @kosukesaigusa