riverpod generatorでwithConverterを作ってみた!
✨tips
FirestoreのwithConverter
なるものをriverpod generatorで使うとデータ型を指定するところではまってしまいます。正しい型を指定しないと、自動生成させてくれない💦
単純にFirestoreのデータ型を理解してれば作れるんでしょうけど単純じゃない...
日々いろいろ探求しながら作ってるんですけど、思いつきで作ったコードを自作してみました。
作ったもの
最近、副業っていうか本業になってるんですけど、ストップウォッチの機能を作るお仕事をしております。この仕事は創業メンバーになって欲しいとお声をかけていただいて始めました。
作り方
まずはデータクラスというかモデルクラス、エンティティっていう人もいるかな。freezed
で作ってFirestoreのデータ構造に合わせて、データの型を定義します。Firestoreのフィールド名と同じものを作ればOK!
TimestampConverter
は自作します。
MVCのコントローラーぽいやつ!
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
class TimestampConverter implements JsonConverter<DateTime?, Timestamp?> {
const TimestampConverter();
DateTime? fromJson(Timestamp? json) => json?.toDate();
Timestamp? toJson(DateTime? object) =>
object == null ? null : Timestamp.fromDate(object);
}
freezed
のモデルクラスはこんな感じです。TaskState
がトップレベルのコレクション用で、Works
がサブコレクション用です。
モデルクラス
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../converter/timestamp_converter.dart';
part 'task_state.freezed.dart';
part 'task_state.g.dart';
// task用のモデル
class TaskState with _$TaskState {
const factory TaskState({
('') String skill,
('') String status,
('') String taskName,
}) = _TaskState;
factory TaskState.fromJson(Map<String, dynamic> json) => _$TaskStateFromJson(json);
}
// 時間の保存用のモデル
class Works with _$Works {
const factory Works({
('') workComment,
() DateTime? workDay,
(0) int workTime,
('') commentId,
}) = _Works;
factory Works.fromJson(Map<String, dynamic> json) => _$WorksFromJson(json);
}
FirestoreとFirebaseAuthで使うプロバイダーが必要ですね。作りましょう。uidを取得するのも入りますね。
Firebase用のプロバイダー
// Firestore用のプロバイダー
FirebaseFirestore firebase(FirebaseRef ref) {
return FirebaseFirestore.instance;
}
// FirebaseAuthを提供するProvider
FirebaseAuth firebaseAuth(FirebaseAuthRef ref) {
return FirebaseAuth.instance;
}
// uidを提供するProvider
String? currentUserUid(CurrentUserUidRef ref) {
return FirebaseAuth.instance.currentUser?.uid;
}
withConverter
を使用するプロバイダーですが、トップレベルのコレクション用は、CollectionReference<モデルクラス名>
を指定して、サブコレクション用は、.doc(id)
だからDocumentReference<モデルクラス名>
を指定する必要があります。
withConverter用のプロバイダー
CollectionReference<TaskState> taskConverter(TaskConverterRef ref) {
return ref.watch(firebaseProvider).collection('prod_task').withConverter(
fromFirestore: (snapshot, _) {
try {
final data = snapshot.data()!;
return TaskState.fromJson(data);
} on Exception catch (e) {
throw Exception(e);
}
},
toFirestore: (user, _) {
return user.toJson();
},
);
}
DocumentReference<Works> worksConverter(WorksConverterRef ref) {
final uid = ref.watch(currentUserUidProvider) ?? '';
return ref
.watch(taskConverterProvider)
.doc(uid)
.collection('prod_works')
.doc(uid)
.withConverter(
fromFirestore: (snapshot, _) {
try {
final data = snapshot.data()!;
return Works.fromJson(data);
} on Exception catch (e) {
throw Exception(e);
}
},
toFirestore: (works, _) {
return works.toJson();
},
);
}
メソッドを定義したクラスでは、コンバーターがないと、.add
と.set
するときに、.toJson
を書かないといけません。でも、もしコンバーターがあるとコードが短くなります。.collecton('task')
とか長いコードをコンバーターに書いて使いまわせるようにしてるから、書く必要がなくなる。
Notifier使わないので、MVVMというより、MVCチックになってます。
Firebase用のプロバイダー
(keepAlive: true)
StopWatchController stopWatchController(StopWatchControllerRef ref) {
return StopWatchController(ref);
}
class StopWatchController {
StopWatchController(this.ref);
final Ref ref;
Future<void> saveStopWatch(Works works) async {
try {
await ref.read(worksConverterProvider).set(works);
} on Exception catch (e) {
logger.d('error: $e');
rethrow;
}
}
}
まとめ
今回は、データ型を指定して、ストップウオッチで使う専用のコンバーターを作りました。記事読んでも中途半端なところがあるので、今回理解して欲しいのは、トップレベルのコレクションIDしか使わないのであれば、CollectionReference
を使い、サブコレクションのときは、DocumentReference
を使います。
Discussion