📱

【Flutter】hiveの後継Isarを試してみる

2022/05/04に公開

isar | Dart Package

hiveのv2系として別プロジェクトで進められている、isarを実際にアプリに組み込んで使う想定で色々試してみたいと思います。

環境セットアップ

pubspec.yaml に以下を追加

dependencies:
  isar: 2.2.1
  isar_flutter_libs: 2.2.1 # contains the binaries

dev_dependencies:
  isar_generator: 2.2.1
  build_runner: any

試しに user テーブルを作成してみる

import 'package:isar/isar.dart';

part 'user.g.dart';

()
class User {
  ()
  int? id;

  late String name;

  String? avatarUri;

  late DateTime createdAt;

  late DateTime updatedAt;
}

build_runner 実行

$ flutter pub run build_runner build

user.g.dart が生成されていればOKです。

スプラッシュ画面中にisar初期化を行う想定で試してみる

実際にisar初期化する場面を想定して、今回はスプラッシュ画面表示中に初期化を実施してみたいと思います。
まずは、flutter_native_splashpath_provider パッケージを追加します。

dependencies:
  flutter_native_splash: ^2.1.6
  path_provider: ^2.0.9

適当なアイコン用意して pubspec.yaml に以下を追加します。

flutter_native_splash:
  color: '#ffffff'
  image: 'assets/images/splash.png'
  fullscreen: true

※ 今回はAndroid12対応など細かい所は省略してます

flutter pub run flutter_native_splash:create コマンド実施してsplash画面を作成。
ver 2.0.3以降から FlutterNativeSplash.remove() 呼ぶまでsplash画面を表示してくれるみたいなので、そちらを使ってisarの初期化を実装してみたいと思います。

main.dart
import 'package:flutter_native_splash/flutter_native_splash.dart';

void main() {
  WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
  FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
  runApp(const MyApp());
}

MyApp 内で初期化 + スプラッシュ画面を非表示にする処理を追加します。

import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';

late Isar isar;

class MyApp extends StatelessWidget {
 const MyApp({Key? key}) : super(key: key);

 Future<void> initialize() async {
   final dir = await getApplicationSupportDirectory();

   isar = await Isar.open(
       schemas: [UserSchema],
       directory: dir.path,
       inspector: true);
   FlutterNativeSplash.remove(); // ← スプラッシュを非表示にする
 }

 
 Widget build(BuildContext context) {
   return FutureBuilder(
       future: initialize(),
       builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
         if (snapshot.connectionState == ConnectionState.waiting) {
           return const Center(child: CircularProgressIndicator());
         }
         return MaterialApp(
           title: 'Flutter Demo',
           theme: ThemeData(
             primarySwatch: Colors.blue,
           ),
           home: const MyHomePage(title: 'Flutter Demo Home Page'),
         );
       });
 }
}

これでスプラッシュ画面後には isar が使える状態になっています。

Riverpodと組み合わせて使ってみる

実際にありそうな Riverpod と組み合わせて使う想定で進めていきたいと思います。

今回は実際にisarを使用する user_service.dart と利用する user_repository.dart を作成します。

  • user_service.dart
    • Riverpodのfamilyを使用して、外部からisarのインスタンスをDIできる様にしてます
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:isar/isar.dart';
import 'package:xxxx/repositories/entities/user.dart';

final ProviderFamily<UserService, Isar> userServiceProvider =
    Provider.family<UserService, Isar>((_, isar) => UserService(isar: isar));

class UserService {
  UserService({required this.isar});

  final Isar isar;

  Future<User?> find() async {
    return isar.users.where().findFirst(); // 1ユーザーしか登録しない想定
  }

  Future<int> add({required String name, String? avatarUri}) async {
    final user = User()
      ..name = name
      ..avatarUri = avatarUri
      ..createdAt = DateTime.now()
      ..updatedAt = DateTime.now();
    return isar.writeTxn((isar) => isar.users.put(user));
  }
}
  • user_repository.dart
    • user_serviceに渡すisarはスプラッシュ画面中に初期化したものを渡しています
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:xxxx/app.dart';
import 'package:xxxx/repositories/entities/user.dart';
import 'package:xxxx/repositories/services/db/user_service.dart';

final Provider<UserRepository> userRepositoryProvider =
    Provider<UserRepository>(
        (ref) => UserRepository(service: ref.watch(userServiceProvider(isar))));

class UserRepository {
  UserRepository({required this.service});

  final UserService service;

  Future<User?> find() => service.find();

  // ユーザー未登録の場合ユーザー作成
  Future<void> initUser() async {
    final user = await find();
    if (user == null) {
      await service.add(name: 'anonymous');
    }
  }
}

あとは ref.watch(userRepositoryProvider) で使って進めていけば良さそうかと思います。

Isar Inspector を使ってみる

isar/README.md at main · isar/isar

実際のデータを参照できる Isar Inspectorを使ってみたいと思います。

まずは リリースページから Isar.Inspector.zip をダウンロードしてきます。

Isar.open する際に inspector: true のパラメータが必要との事なので inspector: true を追加し起動するとコンソールに
以下の様なメッセージが表示されるようになります。

指定されたURLを Isar Inspectorに設定してやると見れるようになります。
↓ Isar Inspectorを起動した様子

バッドノウハウ

build_runner 実行時に以下のエラーが発生する。

line 1, column 22 of package:xxxx/user.dart: Could not resolve annotation for `class User`.
  ╷
1 │ @Collection()
  │ ^^^^^^^^^^^^^
  ╵

公式のサンプルには無い import 'package:isar/isar.dart'; を先頭に追記

参考URL

Discussion