supabaseとは
supabese(スパベース)とは、Firebaseの代替Baasサービスとなります。
Firebaseのコレクション・ドキュメントの形式で管理するものとは違い、PostgreSQLを用いたRDB型のデータ管理が可能です。詳しくは別記事にて紹介しています。
ただし、Supabaseはどんどん成長しているため詳しくはドキュメントを確認してください。
また、supabaseのエンジニアであるタイラーさんもブログや動画で新機能の紹介を積極的にしてくれています。
オフラインサポート(オフラインファースト)の必要性
ビジネスや趣味であっても、ユーザーは電波の届きにくい環境にいることがしばしばあります。
- 地下鉄の中
- オフィスビルの地下
- オフィスビルの高層階
- 地方の山や海
この時に、アプリを利用したいのにネットワークが遅いせいで使えなかった場合ユーザーにとってストレスになり、時には離脱の原因となってしまいます。
そのため、少し労力はありますが上記のケースに該当しそうな場合はオフライン対応を考慮しても良いと思います。
参考ブログ
今回、英文ですがブログで公開されているので、そこからピックアップして紹介します。
実装
1. Supabaseプロジェクトのセットアップ
まずは無料枠で良いのでsupabaseプロジェクトを作成してください。
https://supabase.com/dashboard/projects
supabaseにはローカル開発の環境も整っているのですが、オフラインにする手段が面倒になってしまうため一旦リモートでプロジェクトは用意しましょう。
2. 必要パッケージの導入
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/adapters
とlib/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にもデータが入ることを確認
以上です!
既に導入済みの場合モデルの変更は面倒かもしれませんが、ぜひチャレンジしてみてください!