🎅

flutterfire_gen パッケージについて

2023/12/15に公開

はじめに

flutterfire_gen は、私が開発をしている Flutter の Cloud Firestore のためのコード生成パッケージです。

https://github.com/kosukesaigusa/flutterfire_gen

pub.dev にも publish はしていますが unlisted の状態にしています。最新バージョンはこちら:

https://pub.dev/packages/flutterfire_gen

からご確認ください。

関連パッケージはこちらです:

https://pub.dev/packages/flutterfire_gen_annotation

https://pub.dev/packages/flutterfire_gen_utils

開発途中ですが、

  • 私の個人のプロジェクトのアプリ開発では十分使用できるようになってきていること
  • 2023 年 9 月に行われた東京 Flutter ハッカソンでも活用することで、素早い実装と、初めて一緒に開発するチームメンバーにとっても分かりやすい開発体験を可能にして、優勝することができたこと
  • 2023 年 12 月の GDG DevFest の LT でも初めて対外的に紹介することで、一部の方に興味をもっていただいたこと

など、そろそろ自身以外の人にも触ってみてもらう機会があっても良さそうだという思えるようになってきたので、この記事を書くことにしました。

https://x.com/TYOFlutterHack/status/1708407065146998979?s=20

本記事の内容とも重なりますが、日本語でも README を書いたのでご覧ください。

https://github.com/kosukesaigusa/flutterfire_gen/blob/main/resources/translations/ja_JP/README.md

未完成のパッケージなので、しばらくは破壊的な変更や方針転換の可能性もありますが、先行して触っていただいた方からは色々なアドバイスを頂けると嬉しいです!

GitHub Issue でのご提案やご報告はもちろん、本記事へのコメントや X 等での DM でも結構です。

https://github.com/kosukesaigusa/flutterfire_gen/issues

https://twitter.com/KosukeSaigusa

開発のモチベーション

Flutter と Cloud Firestore を用いて開発を行う時に、次のような課題を感じたことはないでしょうか?

  • 読み込み (read)、作成 (create)、更新 (update)、削除 (delete) の各操作で必要となる最適なインターフェースが異なる
    • 読み込み時には、ドキュメント ID を必ず取得結果に含めたい
    • が、作成時にはドキュメント ID はまだ知り得ないのでインターフェースに含められない
    • また、更新時には指定したフィールドのみを更新したいので、すべて optional なパラメータとしたい
    • 読み込み、作成、更新で異なるデフォルト値を指定したいこともある
  • 総じてボイラプレートコードが多い
    • 型安全な操作のために withConverter を用いて CollectionReferenceDocumentReference をすべてのコレクション・ドキュメントに対して定義し切るのは大変(上記のように、read, create, update のインターフェースを区別するのだとしたらその分だけ定義する必要がある)
    • しばしば書き込み時に自動でサーバタイムスタンプ FieldValue.serverTimestamp() を設定したいフィールドがあるが、それを毎回書くのも面倒
    • Cloud Firestore の Timestamp 型と Dart の DateTime 型の変換を自分で書く必要がある
    • 取得処理や、作成、更新、削除処理も同じようなコードを異なるコレクション・ドキュメントに対して繰り返し書く

flutterfire_gen を使用すると、Cloud Firestore のドキュメントのスキーマに対応するクラスを Dart でたった一つ記述することで、上記の課題や面倒を解決できるボイラプレートコードを自動で生成することができます。

flutterfire_gen の目的

flutterfire_gen を活用することで、できるようになることの主な例は以下の通りです。

  • read, create, update (, delete) でそれぞれに最適なインターフェースを生成する
  • read, create, update, delete の型安全なメソッドを生成する
  • read, create, update 時に異なるデフォルト値を設定できる
  • create, update 時に自動で FieldValue.serverTimestamp() を使用できる
  • create, update 時に実際の値(例:42, [1, 3, 5])と FieldValue(例:FieldValue.increment(1), FieldValue.arrayUnion([7]))とをまとめて取り扱うインターフェースを提供する
  • JsonConverter も使用できる
    • Cloud Firestore の Timestamp 型と Dart の DateTime 型の変換は JsonConverter なしで自動で行う
  • その他にもいろいろ

単に、いわゆるデータクラスを生成するだけではなく、型安全な読み書きメソッドや、FieldValue の取り扱いなどの Cloud Firestore をより便利に・汎用的に使用することができる仕組みを生成します。

使い方

導入

導入したい Flutter プロジェクトの pubspec.yaml に下記のような記述をしてください。

dependencies:
  cloud_firestore: latest

  firebase_core: latest

  # A package containing annotations for flutterfire_gen.
  flutterfire_gen_annotation: latest

  # A package containing utility annotations for flutterfire_gen.
  flutterfire_gen_utils: latest

  # Optional. Will be necessary if you use JsonConverter.
  json_annotation: latest

dev_dependencies:
  # The tool to run code-generators.
  build_runner: latest

  # The code generator.
  flutterfire_gen: latest

の 3 つは flutterfire_gen によるコード生成とそれに関わる機能(アノテーションや機能の拡張)を提供するパッケージです。

@FirestoreDocument アノテーションでドキュメントスキーマを定義する

todos コレクションの Todo ドキュメントのスキーマを flutterfire_gen の記法で記述してみましょう。

要件は以下の通りです。

  • title: Todo のタイトル
    • String
  • isCompleted: Todo の完了状態
    • bool
    • 読み込み時にもし null であれば false とする
    • 作成時に指定しなければ false で作成する
  • createdAt: Todo の作成日時
    • DateTime?
    • ドキュメントの作成時には自動で FieldValue.serverTimestamp() を適用したい
  • updatedAt: Todo の更新日時
    • DateTime?
    • ドキュメントの作成および更新時には自動で FieldValue.serverTimestamp() を適用したい
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutterfire_gen_annotation/flutterfire_gen_annotation.dart';

part 'todo.flutterfire_gen.dart';

(path: 'todos/{todoId}')
class Todo {
  const Todo({
    required this.title,
    required this.isCompleted,
    required this.createdAt,
    required this.updatedAt,
  });

  final String title;

  (false)
  (false)
  final bool isCompleted;

  
  final DateTime? createdAt;

  
  
  final DateTime? updatedAt;
}

まずは Cloud Firestore のドキュメントに対応するクラス Todo@FirestoreDocument のアノテーションを施します。

(path: 'todos/{todoId}')
class Todo { /** 省略 */ }

@FirestoreDocument アノテーションの必須パラメータである path には、該当するドキュメントまでのパスを以下のようなルールで記述してください。

  • スラッシュ区切りでコレクション名とドキュメント ID を交互に書く
  • ドキュメント ID は {} で囲む
  • ドキュメント ID は Id で終わる文字列とする
    • Id 以前の文字列がドキュメント名として認識されます
    • 例:users/{userId} は「user コレクションの user ドキュメント」のスキーマ定義であることを意味する

サブコレクションを用いたネストしたパスも同様に定義することができます。

例:

(path: 'chatRooms/{chatRoomId}/chatMessages/{chatMessageId}')
class ChatMessage { /** 省略 */ }

コンストラクタパラメータの部分はコード生成のロジックでは参照していません(required を指定しようとしまいと、デフォルト値を設定しようとしまいと、生成されるコードに影響はありません)。コンパイルエラーの起きない方法で記述してください。

(path: 'todos/{todoId}')
class Todo {
  const Todo({
    required this.title,
    required this.isCompleted,
    required this.createdAt,
    required this.updatedAt,
  });

  /** 省略 */
}

メンバ変数の定義も通常の Dart の文法に従って行ってください。様々なアノテーションに対応しています。

(path: 'todos/{todoId}')
class Todo {
  /** 省略 */

  final String title;

  (false)
  (false)
  final bool isCompleted;

  
  final DateTime? createdAt;

  
  
  final DateTime? updatedAt;
}

flutterfire_gen では read, update, create 時にそれぞれ異なるデフォルト値を設定することができます。

(false)
(false)
final bool isCompleted;

たとえばこの isCompleted フィールドは

  • read 時に当該フィールドに値がなければ(または null であれば)デフォルトで false にする
  • create 時に当該フィールドの値を指定しなければデフォルトで false を書き込む

のように処理されます。

@alwaysUseFieldValueServerTimestampWhenCreating@alwaysUseFieldValueServerTimestampWhenUpdating のアノテーションを使用すると、create 時や update 時に当該フィールドに自動的に FieldValue.serverTimestamp() が与えられるようになります。


final DateTime? createdAt;



final DateTime? updatedAt;

flutterfire_gen でコードを生成する

コード生成を実行するためには下記のコマンドを実行してください。

flutter pub run build_runner build --delete-conflicting-outputs

また、生成元ファイルの拡張子の直前に .flutterfire_gen を追加したファイルが生成されるので、生成元ファイルには下記のような part 'todo.flutterfire_gen.dart'; の記述がされている必要があります。

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutterfire_gen_annotation/flutterfire_gen_annotation.dart';

part 'todo.flutterfire_gen.dart';

生成された Query クラスを利用する

上記の @FirestoreDocument アノテーションを施した Todo クラスに対してコード生成を行うと、生成結果に TodoQuery というクラスが含まれています。TodoQuery には

read

  • fetchDocuments: todos コレクションの複数のドキュメントを取得する
  • subscribeDocuments: todos コレクションの複数のドキュメントのリアルタイム更新を取得する
  • fetchDocument: todos コレクションの指定したドキュメントを取得する
  • subscribeDocument: todos コレクションの指定したドキュメントのリアルタイム更新を取得する

create/update

  • add: todos コレクションに新しいドキュメントを作成する
  • set: todos コレクションの指定したドキュメントにデータをセットする
  • update: todos コレクションの指定したドキュメントを更新する

delete

  • delete: todos コレクションの指定したドキュメントを削除する

のような基本的な読み書きメソッドが定義されています。

さらに、それらは

  • read で得られる値としての ReadTodo
  • create で作成する際のインターフェースとしての CreateTodo
  • update で更新する際のインターフェースとしての UpdateTodo

が定義されて型安全性が担保されています。

たとえば、@FirestoreDocument アノテーションを施した Todo クラスには特にドキュメント ID の文字列フィールドを定義していませんが、TodoQuery のメソッドを通じて得られる ReadTodo 型のインスタンスには、自動的に non-nullable な String todoId が含まれるようになっています。

一方で、create 時にはこれから作成するドキュメントの ID は知り得ないので、todoId はそのインターフェースに含まれません。

そして、update では指定したフィールドだけを更新したいので、UpdateTodo 型が提供するドキュメントの更新のためのインターフェースのパラメータはすべて optional です。

Todo クラスを一つ定義してコード生成を実行するだけで、読み書きの操作のそれぞれで異なる最適な型と、基本的な読み書きのメソッドが自動で生成されるのが flutterfire_gen を使用することで得られる大きな恩恵です。

read (get/list)

read 操作は下記のようにとてもシンプルに書くことができます。FirebaseFirestore.instance と繰り返し書くことも、CollectionReferenceDocumentReferencewithConverter を施して型安全な操作をするためのコードを自分で書く必要も全くありません。それらのボイラプレートコードはすべて flutterfire_gen が生成します。

final query = TodoQuery();

Future<List<ReadTodo>> fetchTodos() => query.fetchDocuments();

Stream<List<ReadTodo>> subscribeTodos() => query.subscribeDocuments();

Future<ReadTodo?> fetchTodo(String todoId) =>
    query.fetchDocument(todoId: todoId);

Stream<ReadTodo?> subscribeTodo(String todoId) =>
    query.subscribeDocument(todoId: todoId);

読み込みクエリに where 句や orderBy 句を追加するのにも対応しています。各メソッドの任意引数の queryBuilder を持ちいて下記のように各種の条件を追加するだけです。

final query = TodoQuery();

Future<List<ReadTodo>> fetchTodos() => query.fetchDocuments(
      queryBuilder: (query) => query
          .where('isCompleted', isEqualTo: false)
          .orderBy('createdAt', descending: true),
    );

上で説明した通り、Todo クラスの定義をした際には書く必要のなかった todoId が確実に取得できています。

Future<List<ReadTodo>> fetchTodos() async {
  final todos = await query.fetchDocuments();
  for (final todo in todos) {
    print(todo.todoId);
  }
  return todos;
}

create

create 時には、型安全な操作のために CreateTodo という専用のインターフェースが提供されています。

final query = TodoQuery();

Future<DocumentReference<CreateTodo>> addTodo(String title) =>
    query.add(createTodo: CreateTodo(title: title));

Future<DocumentReference<CreateTodo>> addCompletedTodo(String title) =>
    query.add(createTodo: CreateTodo(title: title, isCompleted: true));

Todo の title が必須のパラメータとなっています。isCompleted が任意のパラメータになっているのは、Todo クラスを定義した際に @CreateDefault(false) のアノテーションを施したためです。よって、特に指定しなければ isCompletedfalse でドキュメントが作成されます。

また、createdAt, updatedAt はインターフェースに登場しませんが、内部で自動で FieldValue.serverTimestamp() が適用されています。このあたりを何も意識しなくて良いのも flutterfire_gen が自動でそれらのコードを生成している恩恵です。

update

update 時にも、UpdateTodo という専用のインターフェースが提供されています。

指定したフィールドのみを更新したいので、UpdateTodo に定義されているのはすべて任意引数です。

final query = TodoQuery();

Future<void> updateCompletionStatus({
  required String todoId,
  required bool isCompleted,
}) =>
    query.update(
      todoId: todoId,
      updateTodo: UpdateTodo(isCompleted: isCompleted),
    );

上記は、指定した Todo ドキュメントの完了状態 (isCompleted) を更新する関数です。

ここでも create と同様に、updatedAt には内部で自動で FieldValue.serverTimestamp() が適用されています。

発展

スキーマ定義のクラス名や生成コードのクラス名をカスタマイズする

上記までの例ではスキーマ定義を Todo というクラス名で行い、read, create, update, delete 操作のためのクラスの接頭辞として、それぞれ Read, Create, Update, Delete が付されたものが生成されるようになっていました。

が、

  • Todo という最もそれらしいクラス名をスキーマ定義のために使用してしまうため、他で使用できないこと
  • ReadTodo, CreateTodo, UpdateTodo, DeleteTodo のようなクラス名を強制されずにカスタマイズしたいケースもあること

に対応するために、build.yaml で下記のようにすることで、スキーマ定義のクラス名と生成されるクラス名を一律でカスタマイズできるようになっています。

targets:
  $default:
    builders:
      flutterfire_gen:
        options:
          schema_definition_class_prefix: "_$" # Defaults to ""
          read_class_prefix: "" # Defaults to "Read"
          create_class_prefix: "Create" # Defaults to "Create"
          update_class_prefix: "Update" # Defaults to "Update"
          delete_class_prefix: "Delete" # Defaults to "Delete"
          read_class_suffix: "Dto" # Defaults to ""
          create_class_suffix: "Data" # Defaults to ""
          update_class_suffix: "Interface" # Defaults to ""
          delete_class_suffix: "EtCetera" # Defaults to ""

生成後のコードの接頭辞や接尾辞は @FirestoreDocument アノテーションで個別に設定することもできます。

(
  path: 'todos/{todoId}',
  readClassPrefix: '',
  createClassPrefix: 'Create',
  updateClassPrefix: 'Update',
  deleteClassPrefix: 'Delete',
  readClassSuffix: 'Dto',
  createClassSuffix: 'Data',
  updateClassSuffix: 'Interface',
  deleteClassSuffix: 'EtCetera',
)
class _$Todo { /** 省略 */ }

JsonConverter

json_annotation パッケージの JsonConverter を適用することも可能です。

たとえば、下記の visibility フィールドには @_visibilityConverterJsonConverter のアノテーションが施されており、

  • Dart では enumVisibility
  • Cloud Firestore では String

として扱うための変換をすることができます。

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutterfire_gen_annotation/flutterfire_gen_annotation.dart';
import 'package:json_annotation/json_annotation.dart';

part 'repository.flutterfire_gen.dart';

(path: 'repositories/{repositoryId}')
class Repository {
  Repository({
    required this.visibility,
  });

  
  final Visibility visibility;
}

enum Visibility {
  public,
  private,
  ;

  factory Visibility.fromString(String visibilityString) {
    switch (visibilityString) {
      case 'public':
        return Visibility.public;
      case 'private':
        return Visibility.private;
    }
    throw ArgumentError('visibility is not valid: $visibilityString');
  }
}

const _visibilityConverter = _VisibilityConverter();

class _VisibilityConverter implements JsonConverter<Visibility, String> {
  const _VisibilityConverter();

  
  Visibility fromJson(String json) => Visibility.fromString(json);

  
  String toJson(Visibility visibility) => visibility.name;
}

FieldValue

Cloud Firestore で値を作成したり更新したりする際には、フィールドに 42[1, 3, 5] のように具体的な値を与えることもできますが、

  • num 型のフィールドに対して、現在の値から相対的な値を指定するための FieldValue.increment(1)
  • array 型のフィールドに対して、既になければ指定した値を追加するための FieldValue.arrayUnion([7]) や、指定した値があれば取り除く FieldValue.arrayRemove([5])

のように FieldValue を使用した指定方法もあります。

そのような FieldValue で指定する可能性があるフィールドには @allowFieldValue アノテーションを使用して、下記のように定義することができます。


final int fieldValueAllowedInt;


final List<String> fieldValueAllowedList;

そうすると、CreateFooUpdateFoo のインターフェースとして

  • int 型の代わりに FirestoreData<int>
  • List<String> 型の代わりに FirestoreData<List<String>>

を使用することになります。

FirestoreData 型は flutterfire_gen_utils パッケージで定義された、sealed class で、下記の 2 つ

  • ActualValue: 42[1, 3, 5] のような具体的な値を指定するための型
  • FieldValueData: FieldValue で指定するための型

をまとめています。

よって、たとえば Counter ドキュメントの count という整数フィールドを更新する際には、下記のように実際の値と FieldValue とで更新を実行することができます。

final query = CounterQuery();

Future<void> updateCount(String counterId, int count) => query.update(
      counterId: counterId,
      updateCounter: UpdateCounter(count: ActualValue<int>(count)),
    );

Future<void> incrementCount(String counterId) => query.update(
      counterId: counterId,
      updateCounter:
          UpdateCounter(count: FieldValueData<int>(FieldValue.increment(1))),
    );

活用例

さて、以上までで flutterfire_gen によるスキーマ定義の方法、各種アノテーション、生成される Query クラスの仕様などについて説明しました。

ここでは実際に flutterfire_gen を使用した小さな Todo アプリをサンプルとして取り上げながら説明を加えます。

同じくコード生成の仕組みを活用する riverpod_generator と併用しています。

https://github.com/kosukesaigusa/flutterfire_gen_todo

https://pub.dev/packages/riverpod_generator

サンプルアプリでは次のような機能が実装されています。

  • Todo 一括取得(orderBy 句あり)
  • Todo 作成
  • Todo 更新
  • Todo 削除
  • pull to refresh

todo list

UI については GIF を見るだけで十分察することができる通り、特に説明することはありませんが、このようです:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import 'todo.dart';

class TodoListPage extends ConsumerWidget {
  const TodoListPage({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final todoList = ref.watch(todoListProvider);
    return Scaffold(
      appBar: AppBar(title: const Text('Todo List')),
      body: todoList.when(
        data: (todos) => RefreshIndicator(
          onRefresh: () => ref.refresh(todoListProvider.future),
          child: ListView.builder(
            itemCount: todos.length,
            itemBuilder: (context, index) {
              final todo = todos[index];
              return ListTile(
                key: ValueKey(todo.todoId),
                title: Text(todo.title),
                subtitle: Text(todo.todoId),
                leading: Checkbox(
                  value: todo.isCompleted,
                  onChanged: (value) async {
                    if (value == null) {
                      return;
                    }
                    await ref
                        .read(todoListProvider.notifier)
                        .updateCompletionStatus(
                          todoId: todo.todoId,
                          isCompleted: value,
                        );
                  },
                ),
                trailing: IconButton(
                  onPressed: () =>
                      ref.read(todoListProvider.notifier).delete(todo.todoId),
                  icon: const Icon(Icons.delete),
                ),
              );
            },
          ),
        ),
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (err, stack) => Center(child: Text(err.toString())),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref
            .read(todoListProvider.notifier)
            .addTodo('Todo ${DateTime.now()}'),
        child: const Icon(Icons.add),
      ),
    );
  }
}

驚くべきなのは、UI 以外に記述が必要なソースコードは、riverpod_generator の簡潔さも相まって下記のみで完了してしまうということです。

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutterfire_gen_annotation/flutterfire_gen_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'todo.flutterfire_gen.dart';
part 'todo.g.dart';


TodoQuery todoQuery(TodoQueryRef _) => TodoQuery();


class TodoList extends _$TodoList {
  
  Future<List<Todo>> build() => ref.watch(todoQueryProvider).fetchDocuments(
        queryBuilder: (query) => query.orderBy('createdAt', descending: true),
      );

  Future<void> addTodo(String title) async {
    await ref.read(todoQueryProvider).add(createTodo: CreateTodo(title: title));
    ref.invalidateSelf();
  }

  Future<void> updateCompletionStatus({
    required String todoId,
    required bool isCompleted,
  }) async {
    await ref.read(todoQueryProvider).update(
          todoId: todoId,
          updateTodo: UpdateTodo(isCompleted: isCompleted),
        );
    ref.invalidateSelf();
  }

  Future<void> delete(String todoId) async {
    await ref.read(todoQueryProvider).delete(todoId: todoId);
    ref.invalidateSelf();
  }
}

(path: 'todos/{todoId}')
// ignore: unused_element
class _$Todo {
  const _$Todo({
    required this.title,
    required this.isCompleted,
    required this.createdAt,
    required this.updatedAt,
  });

  final String title;

  (false)
  (false)
  final bool isCompleted;

  
  final DateTime? createdAt;

  
  
  final DateTime? updatedAt;
}

今までに説明した通り、@FirestoreDocument アノテーションで Todo ドキュメントを定義するだけで、TodoQuery というクラスに基本的に読み書きメソッドが定義されているので、これを @riverpodtodoQueryProvider として提供できるようにしています。


TodoQuery todoQuery(TodoQueryRef _) => TodoQuery();

そのため上記のコードでは直接 FirebaseFirestore.instancewithConverter を自分で繰り返し書くこともなければ、自動で FieldValue.serverTimestamp() を適用したいフィールドが作成・更新のインターフェースに登場することもありません。

読み取り時には必ず取得したドキュメント todoIdReadTodo インスタンスに含まれていますし、ドキュメントの作成時には、isCompleted は任意パラメータ・デフォルト false で定義がされています。

それによって、この Todo アプリに必要な取得・作成・更新・削除のすべて振る舞いが、下記のみで簡潔に記述し切ることができています。


class TodoList extends _$TodoList {
  
  Future<List<Todo>> build() => ref.watch(todoQueryProvider).fetchDocuments(
        queryBuilder: (query) => query.orderBy('createdAt', descending: true),
      );

  Future<void> addTodo(String title) async {
    await ref.read(todoQueryProvider).add(createTodo: CreateTodo(title: title));
    ref.invalidateSelf();
  }

  Future<void> updateCompletionStatus({
    required String todoId,
    required bool isCompleted,
  }) async {
    await ref.read(todoQueryProvider).update(
          todoId: todoId,
          updateTodo: UpdateTodo(isCompleted: isCompleted),
        );
    ref.invalidateSelf();
  }

  Future<void> delete(String todoId) async {
    await ref.read(todoQueryProvider).delete(todoId: todoId);
    ref.invalidateSelf();
  }
}

今後の展望

flutterfire_gen の直近の展望・課題としては以下のようなものを考えています。

  • 生成されるコードやアノテーションのより良いインターフェースを模索する → 対応しました
    • 例:現状はスキーマ定義専用のクラスとして Todo という名前をに使用して、そこに Read, Create, Update のような接頭辞を書くインターフェースにつけているので、もっともぴったりな名前である Todo が使えない
  • パッケージ内部の doc comment を充実させる
  • 生成ロジックのユニットテストを強固に網羅的に書く
  • バッチ書き込みに対応したメソッドも生成する → 対応しました
  • 最近公開された dart_firebase_admin 向けのコードも生成できるようにする
  • ...など

先行して触ってみてくださる方がいれば、ご意見やフィードバックをいただけると嬉しいです!

おわりに

2 年以上前から似たような範囲をカバーするものに cloud_firestore_odm があったり、すでに各人のボイラプレートコードのテンプレートなどを活用して同様のことを実現したりしているという声があったりもしますが、flutterfire_gen では、生成されたコード自体も読みやすく、生成物を自分でカスタマイズして使うこともしやすいといったところもポイントです。

密にフィードバックをくださる方、または一緒に開発や改善をすることにご興味のある方などがいらっしゃいましたら X でご連絡いただいても嬉しいです!

https://twitter.com/KosukeSaigusa

flutterfire_gen をよろしくお願いします!

https://github.com/kosukesaigusa/flutterfire_gen

GitHubで編集を提案

Discussion