📿

riverpod generatorでwithConverterを作ってみた!

2024/02/01に公開

✨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を使います。

https://pub.dev/documentation/cloud_firestore/latest/cloud_firestore/DocumentReference-class.html

https://pub.dev/documentation/cloud_firestore/latest/cloud_firestore/CollectionReference-class.html

https://pub.dev/documentation/cloud_firestore/latest/cloud_firestore/CollectionReference/withConverter.html

Discussion