【Flutter】MasamuneフレームワークがFirebaseDataConnectに対応 - mathru.net | Flutter/Unityによるアプリ開発/楽曲・映像制作/素材配布
2024-10-23

【Flutter】MasamuneフレームワークがFirebaseDataConnectに対応

こんにちは。広瀬マサルです。

Flutter上で動作する統合フレームワーク Masamune を随時更新しています。

今回は GoogleI/O2024 で発表されたFirebase上でPostgreSQLを利用可能になる FirebaseDataConnect を Masamuneフレームワーク 上から利用できるパッケージを新しく追加したので紹介します。

masamune_model_firebase_data_connect

Automatically generates GraphQL files for Firebase Data Connect from Collection and Document data schemes so that they can be used in applications.
https://pub.dev https://pub.dev
title
Builder package to automatically generate GraphQL code for Firebase Data Connect.
https://pub.dev https://pub.dev
title

はじめに

Masamuneフレームワーク は「 CLIによるコード生成 」と「 build_runnnerによるコードの自動生成 」を用いて可能な限りコードの記述量を減らし、アプリ開発の安定性と高速性を高めるFlutter上のフレームワークです。

データベースに関しても Firestore をベースにしたNoSQLデータベースを利用し、わずか1行の更新で ランタイムDB と 端末ローカルDB 、 Firestore を切り替えることが可能な仕組みを提供しています。

GoogleI/O2024 で発表されたFirebase上でPostgreSQLを利用可能になる FirebaseDataConnect が2024年10月にパブリックプレビューとなりFlutterでも利用できるようになりました。

Masamuneフレームワーク ではこれに合わせて FirebaseDataConnect を簡単に利用できるようなパッケージを追加しましたので紹介します。

Firestore や 端末ローカルDB での利用と同じ様に下記のような定義でスキーマを定義できます。

/// Value for model.
@freezed
@formValue
@immutable
@firebaseDataConnect
// TODO: Set the path for the collection.
@CollectionModelPath(
  "user",
  permission: [
    AllowReadModelPermissionQuery.allUsers(),
    AllowWriteModelPermissionQuery.allUsers(),
  ],
)
abstract class UserModel with _$UserModel {
  const factory UserModel({
    // TODO: Set the data schema.
    @Default("Guest") String name,
    int? age,
  }) = _UserModel;
  const UserModel._();
  
  ~~~~
}

以前下記の記事で ModelAdapter を 各データモデルごとに設定可能 になったと書きました。

これを利用することで FirebaseDataConnect と Firestore を連携することが可能になります。

実際にやってみよう

それでは実際の実装を行っていきたいと思います。

今回は簡易的にECショップの商品やカテゴリーをスプレッドシートに定義し、それを購入して履歴に残すデモアプリを作成していきます。

Flutterプロジェクトの作成

まず katana create でFlutterプロジェクトを作成します。

katana create net.mathru.dataconnecttest

少し待てばプロジェクトファイルが作成されます。

Firebaseのプロジェクト作成

Firebaseのプロジェクトを作成します。

以前のこの記事に記載している通りの方法でプロジェクト作成までを行います。

FirebaseDataConnectを有効化

Firebaseのプロジェクト作成後FirebaseDataConnectを有効化していきます。

Data Connectを開きます。

image block

新しくCloudSQLのインスタンスを作成しても既存のものを利用するかを選びます。

image block

LocationとインスタンスID、データベース名を決定します。

image block

DataConnectのサービス名を決定します。

image block

DataConnectが利用可能になります。

image block

FlutterプロジェクトでのFirebaseの初期設定

katana.yaml の下記部分のFirebaseのプロジェクトIDとFirebaseDataConnectの有効化の設定を追記します。


# This section contains information related to Firebase.
# Firebase関連の情報を記載します。
firebase:
  # Set the Firebase project ID.
  # FirebaseのプロジェクトIDを設定します。
  project_id: masamune-test

  # Enable Firebase Firestore.
  # Firebase Firestoreを有効にします。
  firestore:
    enable: false

  # Enable Firebase Data Connect.
  # Firebase Data Connectを有効にします。
  dataconnect:
    enable: true # false -> true

katana apply でkatana.yamlを反映させます。

katana apply

これで準備は完了です。

データモデルの作成

引き続きデータモデルを作成していきましょう。

今回は メッセージ(記事)データ 、 カテゴリーデータ 、 ユーザーデータ をそれぞれ作成していきましょう。

まず下記のコマンドで ユーザーデータ を作成します。

katana code collection user

lib/models/user.dart が作成されるので @firebaseDataConnect アノテーションをクラスに付与しましょう。

また、 @CollectionModelPath の permission にテーブルへのアクセス権を付与します。

アクセス権については後述します。今回は全ユーザーが読み取り/書き取り可能な状態にしておきます。

最後に各フィールドを規定します。

// lib/models/user.dart

/// Value for model.
@freezed
@formValue
@immutable
@firebaseDataConnect
// TODO: Set the path for the collection.
@CollectionModelPath(
  "user",
  permission: [
    AllowReadModelPermissionQuery.allUsers(),
    AllowWriteModelPermissionQuery.allUsers(),
  ],
)
abstract class UserModel with _$UserModel {
  const factory UserModel({
    // TODO: Set the data schema.
    @Default("Guest") String name,
    int? age,
  }) = _UserModel;
  const UserModel._();

  factory UserModel.fromJson(Map<String, Object?> json) =>
      _$UserModelFromJson(json);

  /// Query for document.
  ///
  /// ```dart
  /// appRef.model(UserModel.document(id));       // Get the document.
  /// ref.app.model(UserModel.document(id))..load();  // Load the document.
  /// ```
  static const document = _$UserModelDocumentQuery();

  /// Query for collection.
  ///
  /// ```dart
  /// appRef.model(UserModel.collection());       // Get the collection.
  /// ref.app.model(UserModel.collection())..load();  // Load the collection.
  /// ref.app.model(
  ///   UserModel.collection().data.equal(
  ///     "data",
  ///   )
  /// )..load(); // Load the collection with filter.
  /// ```
  static const collection = _$UserModelCollectionQuery();

  /// Query for form value.
  ///
  /// ```dart
  /// ref.app.form(UserModel.form(UserModel()));    // Get the form controller in app scope.
  /// ref.page.form(UserModel.form(UserModel()));    // Get the form controller in page scope.
  /// ```
  static const form = _$UserModelFormQuery();
}

続いて下記のコマンドで カテゴリーデータ を作成します。

katana code collection category

lib/models/category.dart が作成されるのでユーザーデータと同様の記述を行い各フィールドを規定します。

// lib/models/cateogry.dart

/// Value for model.
@freezed
@formValue
@immutable
@firebaseDataConnect
// TODO: Set the path for the collection.
@CollectionModelPath(
  "category",
  permission: [
    AllowReadModelPermissionQuery.allUsers(),
    AllowWriteModelPermissionQuery.allUsers(),
  ],
)
abstract class CategoryModel with _$CategoryModel {
  const factory CategoryModel({
    // TODO: Set the data schema.
    required String name,
    String? description,
  }) = _CategoryModel;
  const CategoryModel._();

  factory CategoryModel.fromJson(Map<String, Object?> json) =>
      _$CategoryModelFromJson(json);

  /// Query for document.
  ///
  /// ```dart
  /// appRef.model(CategoryModel.document(id));       // Get the document.
  /// ref.app.model(CategoryModel.document(id))..load();  // Load the document.
  /// ```
  static const document = _$CategoryModelDocumentQuery();

  /// Query for collection.
  ///
  /// ```dart
  /// appRef.model(CategoryModel.collection());       // Get the collection.
  /// ref.app.model(CategoryModel.collection())..load();  // Load the collection.
  /// ref.app.model(
  ///   CategoryModel.collection().data.equal(
  ///     "data",
  ///   )
  /// )..load(); // Load the collection with filter.
  /// ```
  static const collection = _$CategoryModelCollectionQuery();

  /// Query for form value.
  ///
  /// ```dart
  /// ref.app.form(CategoryModel.form(CategoryModel()));    // Get the form controller in app scope.
  /// ref.page.form(CategoryModel.form(CategoryModel()));    // Get the form controller in page scope.
  /// ```
  static const form = _$CategoryModelFormQuery();
}

最後に下記のコマンドで メッセージ(記事)データ を作成します。

katana code collection post

lib/models/post.dart が作成されるのでユーザーデータと同様の記述を行い各フィールドを規定します。

フィールドを規定しますが、 メッセージ(記事)データ については作成者としての ユーザーデータ と記事のカテゴリーとしての カテゴリーデータ をそれぞれ参照として紐づけます。

// lib/models/post.dart

/// Value for model.
@freezed
@formValue
@immutable
@firebaseDataConnect
// TODO: Set the path for the collection.
@CollectionModelPath(
  "post",
  permission: [
    AllowReadModelPermissionQuery.allUsers(),
    AllowWriteModelPermissionQuery.allUsers(),
  ],
)
abstract class PostModel with _$PostModel {
  const factory PostModel({
    // TODO: Set the data schema.
    required String title,
    String? body,
    @Default(ModelTimestamp()) ModelTimestamp createdTime,
    @refParam UserModelRef user,
    @refParam CategoryModelRef category,
  }) = _PostModel;
  const PostModel._();

  factory PostModel.fromJson(Map<String, Object?> json) =>
      _$PostModelFromJson(json);

  /// Query for document.
  ///
  /// ```dart
  /// appRef.model(PostModel.document(id));       // Get the document.
  /// ref.app.model(PostModel.document(id))..load();  // Load the document.
  /// ```
  static const document = _$PostModelDocumentQuery();

  /// Query for collection.
  ///
  /// ```dart
  /// appRef.model(PostModel.collection());       // Get the collection.
  /// ref.app.model(PostModel.collection())..load();  // Load the collection.
  /// ref.app.model(
  ///   PostModel.collection().data.equal(
  ///     "data",
  ///   )
  /// )..load(); // Load the collection with filter.
  /// ```
  static const collection = _$PostModelCollectionQuery();

  /// Query for form value.
  ///
  /// ```dart
  /// ref.app.form(PostModel.form(PostModel()));    // Get the form controller in app scope.
  /// ref.page.form(PostModel.form(PostModel()));    // Get the form controller in page scope.
  /// ```
  static const form = _$PostModelFormQuery();
}

下記コマンドでモデル用のコードを自動生成します。

katana code generate

これによりDataConnectの GraphQLスキーマ定義 や FirebaseDataConnectModelAdapter の中身、 DataConnectのDart側のコード がまとめて生成されます。

作成されたスキーマのデプロイ

自動生成されたスキーマをサーバー上にデプロイする必要があります。その場合は下記コマンドを実行してください。

katana deploy

デプロイ中にマイグレーションが必要な状態になることがあります。その場合は firebase フォルダに入り下記コマンドを実行してください。

# firebase
firebase dataconnect:sql:migrate

これで自由に各データモデルを扱えるようになりました。

データを扱うページの作成

定義したモデルを扱うためのページを作成していきましょう。

lib/pages/home.dart を下記のように編集して、データの作成や編集を行えるようにしましょう。

// pages/home.dart

// ignore: unused_import, unnecessary_import
import 'package:dataconnecttest/models/category.dart';
import 'package:dataconnecttest/models/post.dart';
import 'package:dataconnecttest/models/user.dart';
import 'package:flutter/material.dart';
// ignore: unused_import, unnecessary_import
import 'package:masamune/masamune.dart';
import 'package:masamune_universal_ui/masamune_universal_ui.dart';

// ignore: unused_import, unnecessary_import
import 'package:dataconnecttest/main.dart';

part 'home.page.dart';

@immutable
@PagePath("/")
class HomePage extends PageScopedWidget {
  const HomePage({
    super.key,
  });

  /// Used to transition to the HomePage screen.
  ///
  /// ```dart
  /// router.push(HomePage.query(parameters));    // Push page to HomePage.
  /// router.replace(HomePage.query(parameters)); // Replace page to HomePage.
  /// ```
  @pageRouteQuery
  static const query = _$HomePageQuery();

  @override
  Widget build(BuildContext context, PageRef ref) {
    // Describes the process of loading
    // and defining variables required for the page.
    const userId = "user1";
    final user = ref.app.model(UserModel.document(userId))..load();
    final posts = ref.app.model(PostModel.collection())..load();
    final categories = ref.app.model(CategoryModel.collection())..load();

    // Describes the structure of the page.
    return UniversalScaffold(
      appBar: UniversalAppBar(title: Text(l().appTitle)),
      body: UniversalListView(
        children: [
          const ListTile(
            title: Text(
              "User Data",
              style: TextStyle(fontWeight: FontWeight.bold),
            ),
          ),
          Divider(color: theme.color.outline.withOpacity(0.25)),
          if (user.value != null)
            ListTile(
              title: Text(user.value?.name ?? ""),
              subtitle: Text(user.value?.age?.toStringAsFixed(0) ?? ""),
              trailing: SizedBox(
                width: 80,
                child: FormNumField(
                  initialValue: user.value?.age,
                  onChanged: (value) async {
                    await user.save(
                      user.value?.copyWith(age: value?.toInt()),
                    );
                  },
                  picker: const FormNumFieldPicker(
                    begin: 1,
                    end: 100,
                    interval: 1,
                  ),
                ),
              ),
            )
          else
            ListTile(
              leading: const Icon(Icons.add),
              title: const Text("Create user data"),
              onTap: () async {
                await user
                    .save(
                      UserModel(
                        name: "User at ${DateTime.now().yyyyMMddHHmmss()}",
                        age: 20,
                      ),
                    )
                    .showIndicator(context);
              },
            ),
          const Divider(),
          const ListTile(
            title: Text(
              "Post Data",
              style: TextStyle(fontWeight: FontWeight.bold),
            ),
          ),
          Divider(color: theme.color.outline.withOpacity(0.25)),
          ...posts.mapListenable((post) {
            return ListTile(
              title: Text(post.value?.title ?? ""),
              subtitle: Text(
                "${post.value?.category?.value?.name ?? ""} @${post.value?.user?.value?.name ?? ""}",
              ),
            );
          }),
          Divider(color: theme.color.outline.withOpacity(0.25)),
          ...categories.map((category) {
            return ListTile(
              leading: const Icon(Icons.add),
              title: Text(category.value?.name ?? ""),
              onTap: () async {
                final newPost = posts.create();
                await newPost
                    .save(
                      PostModel(
                        title: "Post at ${DateTime.now().yyyyMMddHHmmss()}",
                        category: category,
                        user: user,
                      ),
                    )
                    .showIndicator(context);
              },
            );
          }),
          ListTile(
            leading: const Icon(Icons.add),
            title: const Text("Create category data"),
            onTap: () async {
              final category = categories.create();
              await category
                  .save(
                    CategoryModel(
                      name: "Category at ${DateTime.now().yyyyMMddHHmmss()}",
                    ),
                  )
                  .showIndicator(context);
            },
          ),
        ],
      ),
    );
  }
}

デフォルトのModelAdapterの変更

デフォルトの ModelAdapter を新しく自動生成された FirebaseDataConnectModelAdapter に変更します。

lib/adapter.dart を開き下記を編集してください。


/// App Model.
///
/// By replacing this with another adapter, the data storage location can be changed.
// TODO: Change the database.
const modelAdapter = firebaseDataConnectModelAdapter;
// final modelAdapter = RuntimeModelAdapter();

これで実装完了です。

アプリの実行

アプリを実行して確認してみましょう。

image block

Create user data をタップするとユーザーデータが作成されます。

image block

DataConnectのコンソールからデータを見てみるとデータが入っていることがわかります。

image block

ドロップダウンで 1 を選択すると age が 1 に変わっていることがわかります。

image block

フィルタークエリの作り方

バージョン 2.1.8 から条件を指定したクエリを作成することができるようになりました。

CollectionModelPath の query に ModelDatabaseQueryGroup を作成することで実現可能です。

試しに PostModel を改修しcreatedTimeでフィルタできるクエリを作成してみましょう。

lib/models/post.dart を開き下記のように編集します。

ModelDatabaseQueryGroup にはクエリ自体の名前とクエリの条件を記載します。

// lib/models/post.dart

/// Value for model.
@freezed
@formValue
@immutable
@firebaseDataConnect
// TODO: Set the path for the collection.
@CollectionModelPath(
  "post",
  permission: [
    AllowReadModelPermissionQuery.allUsers(),
    AllowWriteModelPermissionQuery.allUsers(),
  ],
  query: [
    ModelDatabaseQueryGroup(
      name: "createdTime",
      conditions: [
        ModelDatabaseConditionQuery.greaterThanOrEqualTo("createdTime"),
      ],
    ),
  ],
)
abstract class PostModel with _$PostModel {
  const factory PostModel({
    // TODO: Set the data schema.
    required String title,
    String? body,
    @Default(ModelTimestamp()) ModelTimestamp createdTime,
    @refParam UserModelRef user,
    @refParam CategoryModelRef category,
  }) = _PostModel;
  const PostModel._();

  factory PostModel.fromJson(Map<String, Object?> json) =>
      _$PostModelFromJson(json);

  /// Query for document.
  ///
  /// ```dart
  /// appRef.model(PostModel.document(id));       // Get the document.
  /// ref.app.model(PostModel.document(id))..load();  // Load the document.
  /// ```
  static const document = _$PostModelDocumentQuery();

  /// Query for collection.
  ///
  /// ```dart
  /// appRef.model(PostModel.collection());       // Get the collection.
  /// ref.app.model(PostModel.collection())..load();  // Load the collection.
  /// ref.app.model(
  ///   PostModel.collection().data.equal(
  ///     "data",
  ///   )
  /// )..load(); // Load the collection with filter.
  /// ```
  static const collection = _$PostModelCollectionQuery();

  /// Query for form value.
  ///
  /// ```dart
  /// ref.app.form(PostModel.form(PostModel()));    // Get the form controller in app scope.
  /// ref.page.form(PostModel.form(PostModel()));    // Get the form controller in page scope.
  /// ```
  static const form = _$PostModelFormQuery();
}

この状態で下記コマンドでモデル用のコードを自動生成します。

するとGraphQLにクエリが追加され、Dartコードにもクエリを実行するためのメソッドとそれに対応する FirebaseDataConnectModelAdapter の実装が追加されます。

katana code generate

それでは pages/home.dart を編集して追加した条件でデータを取得してみましょう。

PostModel.collection() から createdTimeQuery というメソッドが利用可能になっているのでそれを指定します。

createdTimeGreaterThanOrEqualTo にはこの時間からデータを表示するための閾値を指定してください。ひとまず2024年11月25日を指定します。こうすることで2024年11月25日以前のデータが表示されなくなります。

// pages/home.dart

// ignore: unused_import, unnecessary_import
import 'package:dataconnecttest/models/category.dart';
import 'package:dataconnecttest/models/post.dart';
import 'package:dataconnecttest/models/user.dart';
import 'package:flutter/material.dart';
// ignore: unused_import, unnecessary_import
import 'package:masamune/masamune.dart';
import 'package:masamune_universal_ui/masamune_universal_ui.dart';

// ignore: unused_import, unnecessary_import
import 'package:dataconnecttest/main.dart';

part 'home.page.dart';

@immutable
@PagePath("/")
class HomePage extends PageScopedWidget {
  const HomePage({
    super.key,
  });

  /// Used to transition to the HomePage screen.
  ///
  /// ```dart
  /// router.push(HomePage.query(parameters));    // Push page to HomePage.
  /// router.replace(HomePage.query(parameters)); // Replace page to HomePage.
  /// ```
  @pageRouteQuery
  static const query = _$HomePageQuery();

  @override
  Widget build(BuildContext context, PageRef ref) {
    // Describes the process of loading
    // and defining variables required for the page.
    const userId = "user1";
    final user = ref.app.model(UserModel.document(userId))..load();
    final posts = ref.app.model(PostModel.collection().createdTimeQuery(
      createdTimeGreaterThanOrEqualTo: const ModelTimestamp.dateTime(2024, 11, 25),
    ))
      ..load();
    final categories = ref.app.model(CategoryModel.collection())..load();

    // Describes the structure of the page.
    return UniversalScaffold(
      appBar: UniversalAppBar(title: Text(l().appTitle)),
      body: UniversalListView(
        children: [
          const ListTile(
            title: Text(
              "User Data",
              style: TextStyle(fontWeight: FontWeight.bold),
            ),
          ),
          Divider(color: theme.color.outline.withOpacity(0.25)),
          if (user.value != null)
            ListTile(
              title: Text(user.value?.name ?? ""),
              subtitle: Text(user.value?.age?.toStringAsFixed(0) ?? ""),
              trailing: SizedBox(
                width: 80,
                child: FormNumField(
                  initialValue: user.value?.age,
                  onChanged: (value) async {
                    await user.save(
                      user.value?.copyWith(age: value?.toInt()),
                    );
                  },
                  picker: const FormNumFieldPicker(
                    begin: 1,
                    end: 100,
                    interval: 1,
                  ),
                ),
              ),
            )
          else
            ListTile(
              leading: const Icon(Icons.add),
              title: const Text("Create user data"),
              onTap: () async {
                await user
                    .save(
                      UserModel(
                        name: "User at ${DateTime.now().yyyyMMddHHmmss()}",
                        age: 20,
                      ),
                    )
                    .showIndicator(context);
              },
            ),
          const Divider(),
          const ListTile(
            title: Text(
              "Post Data",
              style: TextStyle(fontWeight: FontWeight.bold),
            ),
          ),
          Divider(color: theme.color.outline.withOpacity(0.25)),
          ...posts.mapListenable((post) {
            return ListTile(
              title: Text(post.value?.title ?? ""),
              subtitle: Text(
                "${post.value?.category?.value?.name ?? ""} @${post.value?.user?.value?.name ?? ""}",
              ),
            );
          }),
          Divider(color: theme.color.outline.withOpacity(0.25)),
          ...categories.map((category) {
            return ListTile(
              leading: const Icon(Icons.add),
              title: Text(category.value?.name ?? ""),
              onTap: () async {
                final newPost = posts.create();
                await newPost
                    .save(
                      PostModel(
                        title: "Post at ${DateTime.now().yyyyMMddHHmmss()}",
                        category: category,
                        user: user,
                      ),
                    )
                    .showIndicator(context);
              },
            );
          }),
          ListTile(
            leading: const Icon(Icons.add),
            title: const Text("Create category data"),
            onTap: () async {
              final category = categories.create();
              await category
                  .save(
                    CategoryModel(
                      name: "Category at ${DateTime.now().yyyyMMddHHmmss()}",
                    ),
                  )
                  .showIndicator(context);
            },
          ),
        ],
      ),
    );
  }
}

ModelDatabaseQueryGroup の conditions に複数条件を指定することで複雑なクエリを実行可能です。(2024年11月27日現在はAND条件のみ)

また、 ModelDatabaseQueryGroup を複数指定すると様々な条件を設定することができます。

Firestoreとの連携

それではFirestoreとFirebaseDataConnectの連携を試してみましょう。

FirebaseDataConnect上で作成した UserModel を PostModel に紐づけ、Firestoreのリアルタイムアップデートで PostModel を常時更新できるようにしましょう。

Firestoreを有効化

まずFirestoreを有効化しましょう。

以前のこの記事に記載している通りの方法でFirestoreの有効化を行います。

FlutterプロジェクトでのFirestoreの初期設定

katana.yaml の下記部分のFirestoreの有効化の設定を追記します。


# This section contains information related to Firebase.
# Firebase関連の情報を記載します。
firebase:
  # Set the Firebase project ID.
  # FirebaseのプロジェクトIDを設定します。
  project_id: masamune-test

  # Enable Firebase Firestore.
  # Firebase Firestoreを有効にします。
  firestore:
    enable: true # false -> true

  # Enable Firebase Data Connect.
  # Firebase Data Connectを有効にします。
  dataconnect:
    enable: true 

katana apply でkatana.yamlを反映させます。

katana apply

ModelAdapterの変更

UserModel に FirebaseDataConnectModelAdapter を設定しベースのModelAdapterにリアルタイムアップデート可能な ListenableFirestoreModelAdapter を設定します。

まず lib/models/user.dart を開き、 CollectionModelPath の adapter に firebaseDataConnectModelAdapter を設定します。

// lib/models/user.dart

/// Value for model.
@freezed
@formValue
@immutable
@firebaseDataConnect
// TODO: Set the path for the collection.
@CollectionModelPath(
  "user",
  permission: [
    AllowReadModelPermissionQuery.allUsers(),
    AllowWriteModelPermissionQuery.allUsers(),
  ],
  adapter: firebaseDataConnectModelAdapter, // Add here
)
abstract class UserModel with _$UserModel {
  const factory UserModel({
    // TODO: Set the data schema.
    @Default("Guest") String name,
    int? age,
  }) = _UserModel;
  const UserModel._();

さらに lib/adapter.dart を開き下記を編集してください。


/// App Model.
///
/// By replacing this with another adapter, the data storage location can be changed.
// TODO: Change the database.
final modelAdapter = ListenableFirestoreModelAdapter(
  options: DefaultFirebaseOptions.currentPlatform,
);
// const modelAdapter = firebaseDataConnectModelAdapter;
// final modelAdapter = RuntimeModelAdapter();

最後に UserModel の CollectionModelPath への変更を反映するためにコードの自動生成を行います。

katana code generate

これで実装完了です。

アプリの実行

アプリを実行して確認してみましょう。

image block

カテゴリーを作ります。

image block

作ったカテゴリーからポストを作成します。

image block

ユーザーが紐づけられて表示されていることがわかります。

Firestoreのデータとしては下記のようになっています。

image block

title を変えてみましょう。

image block

アプリ内をなにも触らずとも下記のようにPostDataのタイトルが「 Changed Title 」になっていることが確認できます。

image block

このようにFirebaseDataConnectのデータに紐づけながらFirestoreのデータを扱うことができました。

おわりに

FirebaseにDataConnectがやってきたおかげでFirebase内でも気軽にRDBを触ることができるようになりました。

Masamuneフレームワーク を用いることでそれをさらに扱いやすくFirestore等と連携しやすいようにすることができます。

自分で使う用途で作ったものですが実装の思想的に合ってそうならぜひぜひ使ってみてください!

また、 こちら にソースを公開しているのでissueやPullRequestをお待ちしてます!

また仕事の依頼等ございましたら、私の Twitter や Webサイト で直接ご連絡をお願いいたします!

FlutterやUnityを使ったアプリ開発やアプリの紹介。制作した楽曲・映像の紹介。画像や映像素材の配布。仕事の受注なども行っています。
https://mathru.net https://mathru.net
title

GitHub Sponsors

スポンサーを随時募集してます。ご支援お待ちしております!

Developed the katana/masamune framework, which has dramatically improved the overall efficiency of Flutter-based application development.
https://github.com https://github.com
title
【アルバム】sing a song

Works かにみそP mathru 音楽 アルバム Vocaloid 鏡音レン

◀︎ Next
【お知らせ】アルバム「sing a song」がサブスク配信

Blog mathru かにみそP 音楽 アルバム 鏡音レン

▶︎ Previous