10
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

kurogoma939のひとりアドベントカレンダーAdvent Calendar 2024

Day 14

【Flutter】supabaseとbrick併用でオフラインサポートが可能になった

Last updated at Posted at 2024-12-13

supabaseとは

supabese(スパベース)とは、Firebaseの代替Baasサービスとなります。
Firebaseのコレクション・ドキュメントの形式で管理するものとは違い、PostgreSQLを用いたRDB型のデータ管理が可能です。詳しくは別記事にて紹介しています。

ただし、Supabaseはどんどん成長しているため詳しくはドキュメントを確認してください。

また、supabaseのエンジニアであるタイラーさんもブログや動画で新機能の紹介を積極的にしてくれています。

オフラインサポート(オフラインファースト)の必要性

ビジネスや趣味であっても、ユーザーは電波の届きにくい環境にいることがしばしばあります。

  • 地下鉄の中
  • オフィスビルの地下
  • オフィスビルの高層階
  • 地方の山や海

この時に、アプリを利用したいのにネットワークが遅いせいで使えなかった場合ユーザーにとってストレスになり、時には離脱の原因となってしまいます。

そのため、少し労力はありますが上記のケースに該当しそうな場合はオフライン対応を考慮しても良いと思います。

参考ブログ

今回、英文ですがブログで公開されているので、そこからピックアップして紹介します。

実装

1. Supabaseプロジェクトのセットアップ

まずは無料枠で良いのでsupabaseプロジェクトを作成してください。
https://supabase.com/dashboard/projects

supabaseにはローカル開発の環境も整っているのですが、オフラインにする手段が面倒になってしまうため一旦リモートでプロジェクトは用意しましょう。

2. 必要パッケージの導入

pubspec.yamlに以下のパッケージを導入しましょう。
ブログに書いてあるものより追加で必要なようなのでまとめて記載します。

pubspec.yaml
dependencies:
  brick_offline_first_with_supabase: ^1.0.0
  sqflite: ^2.3.0
  brick_sqlite: ^3.1.0
  uuid: ^3.0.4
  brick_supabase: ^1.1.0
  supabase_flutter: ^2.2.0
  sqflite_common: ^2.5.4+5

dev_dependencies:
  brick_offline_first_with_supabase_build: ^1.0.0
  build_runner: ^2.4.0

3. brick関連のディレクトリ作成

以下のコマンドでbrick関連のコードを格納するディレクトリを作成します。
また、モデル等を自動生成するためそちらのコードも格納されます。

mkdir -p lib/brick/adapters lib/brick/db;

4. モデルクラスを作成

オフライン対応したいテーブル用のモデルを作成します。
コメントにもある通り、対象ファイルは.model.dartという拡張子としてください。

まずはファイルを作りましょう。

mkdir lib/models && touch lib/models/note.model.dart 
// Your model definition can live anywhere in lib/**/* as long as it has the .model.dart suffix
// Assume this file is saved at my_app/lib/src/users/user.model.dart

import 'package:brick_offline_first_with_supabase/brick_offline_first_with_supabase.dart';
import 'package:brick_sqlite/brick_sqlite.dart';
import 'package:brick_supabase/brick_supabase.dart';
import 'package:uuid/uuid.dart';

@ConnectOfflineFirstWithSupabase(
  supabaseConfig: SupabaseSerializable(tableName: 'memos'),
)
class Memo extends OfflineFirstWithSupabaseModel {
  // 主キーの設定
  @Supabase(unique: true)
  @Sqlite(index: true, unique: true)
  final String id;

  final String text;
  final DateTime createdAt;
  
  Memo({
    String? id,
    required this.text,
    required this.createdAt,
  }) : this.id = id ?? const Uuid().v4();
}

完了したらbuild_runnerで自動生成します

dart run build_runner build

自動生成が完了すると、lib/brick/adapterslib/brick/dbにそれぞれadapter用のクラスとマイグレーションファイルが生成されます。

5. Repositoryクラスの実装

こちらはブログに記載のものを流用して実装します。
主にSupabaseとBrickの機能を用いるためのものなので命名はカスタムしてください。

ではファイルの作成です。

touch lib/brick/repository.dart
// Saved in my_app/lib/src/brick/repository.dart
import 'package:brick_offline_first_with_supabase/brick_offline_first_with_supabase.dart';
import 'package:brick_sqlite/brick_sqlite.dart';
// This hide is for Brick's @Supabase annotation; in most cases,
// supabase_flutter **will not** be imported in application code.
import 'package:brick_supabase/brick_supabase.dart' hide Supabase;
import 'package:sqflite_common/sqlite_api.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

import 'brick.g.dart';
import 'db/schema.g.dart';

class Repository extends OfflineFirstWithSupabaseRepository {
  static late Repository? _instance;

  Repository._({
    required super.supabaseProvider,
    required super.sqliteProvider,
    required super.migrations,
    required super.offlineRequestQueue,
    super.memoryCacheProvider,
  });

  factory Repository() => _instance!;

  static Future<void> configure(DatabaseFactory databaseFactory) async {
    final (client, queue) = OfflineFirstWithSupabaseRepository.clientQueue(
      databaseFactory: databaseFactory,
    );

    await Supabase.initialize(
      url: supabaseUrl,
      anonKey: supabaseAnonKey,
      httpClient: client,
    );

    final provider = SupabaseProvider(
      Supabase.instance.client,
      modelDictionary: supabaseModelDictionary,
    );

    _instance = Repository._(
      supabaseProvider: provider,
      sqliteProvider: SqliteProvider(
        'my_repository.sqlite',
        databaseFactory: databaseFactory,
        modelDictionary: sqliteModelDictionary,
      ),
      migrations: migrations,
      offlineRequestQueue: queue,
      // Specify class types that should be cached in memory
      memoryCacheProvider: MemoryCacheProvider(),
    );
  }
}

6. main.dartの修正

主にsupabaseの初期化処理が必要です。

import 'package:my_app/brick/repository.dart';
import 'package:sqflite/sqflite.dart' show databaseFactory;

Future<void> main() async {
  await Repository.configure(databaseFactory);
  await Repository().initialize();
  runApp(MyApp());
}

7. データのやり取りの方法

データ取得(一覧)

await Repository().get<List<Memo>>();

データ取得(単体)

await Repository().get<Memo>(query: Query.where('id', 'xxxx'));

データ取得(リレーションで絞り込み)

  • user_idカラムでusersテーブルとリレーション設定をしている場合
await Repository().get<Memo>(query: Query.where('user', Where.exact('id', 'xxxx'));

Streamでの取得

final Stream<List<Memo>> memosStream = Repository().subscribe<Memo>();

データ登録

await Repository().upsert<Memo>(
    Memo(
        text: 'xxxx',
        createdAt: DateTime.now().toIso8601String(),
    )
);

データ削除

await Repository().delete<Memo>(Memoモデルを指定);

7. 動作確認

1. まずは登録してみる

2. PCのWi-Fiをオフにする

3. 登録処理をしてみる

  • ここで登録処理が成功し、FutureやStreamでの取得も成功することを確認

4. supabaseダッシュボードを確認

  • ここではまだsupabaseにデータがないことを確認

5. オンラインにする

6. 少ししたらsupabaseにもデータが入ることを確認

以上です!

既に導入済みの場合モデルの変更は面倒かもしれませんが、ぜひチャレンジしてみてください!

10
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?