📲

【Flutter】Isarで初期データ投入を試す

2022/05/10に公開

やりたい事

アプリ初回起動時に、必要な初期データなどを事前に登録しておきたい!
※ API経由などではなく、アプリ内に初期データ用のjsonファイルを用意してデータ投入する想定です

試した方法

isar/samples: Sample apps using Isar
↑で、初期データが無い場合、 assets/quotes.json から読込んで登録を行なっていたので、これにならって進めてみたいと思います。

環境構築

今回は fvm を使用し以下の環境で実施します。

macOS Monterey バージョン12.1 Apple M1
iOS15.0 iPhone 13 シュミレータ
Flutter SDK version: 2.10.3
Dart SDK version: 2.16.1

まずは空のFlutterプロジェクトを作成し、以下isar関連のパッケージを追加します。

dependencies:
  isar: 2.2.1
  isar_flutter_libs: 2.2.1 # contains the binaries
  path_provider: ^2.0.9

dev_dependencies:
  isar_generator: 2.2.1
  build_runner: any

Spotスキーマ作成

今回は↓のSpotスキーマを作成し、Spotの初期データを登録していきます。
(「事前に世界中の有名なスポットを登録しておく必要がある」 といった、ありそう(?)な前提で進めます。)

import 'package:isar/isar.dart';

part 'spot.g.dart';

()
class Spot {
  int? id;

  ()
  late String name;

  late double longitude;

  late double latitude;

  late DateTime createdAt;

  late DateTime updatedAt;
}

build_runner 実施し、spot.g.dart を作成しときます。

$ flutter pub run build_runner build

Isar初期化

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final dir = await getApplicationSupportDirectory();

  final isar = await Isar.open(
      schemas: [SpotSchema], directory: dir.path, inspector: true);
  runApp(const MyApp());
}

※ Isar Inspector で参照する為 inspector: true を設定してます

Asset内のjsonファイル読み込んでDB書き込み

Future<void> _loadSpots(Isar isar) async {
  try {
    final bytes = await rootBundle.load('assets/spots.json');
    final jsonStr = const Utf8Decoder().convert(bytes.buffer.asUint8List());
    final json = jsonDecode(jsonStr) as List;
    final now = DateTime.now();
    final spots = json.map((e) => Spot()
      ..name = e['name']
      ..longitude = double.parse(e['longitude'])
      ..latitude = double.parse(e['latitude'])
      ..createdAt = now
      ..updatedAt = now);
    isar.writeTxn((isar) async {
      await isar.spots.putAll(spots.toList());
    });
  } catch (e) {
    debugPrint(e.toString());
  }
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final dir = await getApplicationSupportDirectory();

  final isar = await Isar.open(
      schemas: [SpotSchema], directory: dir.path, inspector: true);
  await _loadSpots(isar); // ← 追加

  runApp(const MyApp());
}

_loadSpots に関しては Isar sampleのこちらを少しカスタムしたもので、やってる事は同じです。

assets/spots.json の中身は単純な内容になってます ↓

[
  {
    "name": "アテネ",
    "longitude": "23.718989",
    "latitude": "37.973620"
  },
  {
    "name": "イスタンブール",
    "longitude": "29.006069",
    "latitude": "41.065960"
  },
  {
    "name": "ウィーン",
    "longitude": "16.364571",
    "latitude": "48.201841"
  }
]

pubspec.yamlassets/spots.json を設定します。

flutter:
  assets:
    - assets/spots.json

いざアプリ起動して、Isar Inspector で登録できているか確認します。

↑ちゃんとjsonで設定したデータが登録されています ✨

isarが用意している importJson を使う

実はisarにはjsonファイルからimport出来そうな importJson, importJsonRaw, importJsonSync, importJsonRawSync が用意されてます。
https://github.com/isar/isar/blob/cb047f18c9f7711de4bc41832327a4e1c1b4d934/packages/isar/lib/src/native/isar_collection_impl.dart#L383

こちらも試してみたいと思います 👀
先ほどの _loadSpots を以下に修正します。

Future<void> _loadSpots(Isar isar) async {
  try {
    final bytes = await rootBundle.load('assets/spots.json');
    final jsonStr = const Utf8Decoder().convert(bytes.buffer.asUint8List());
    final json = jsonDecode(jsonStr) as List;
    final now = DateTime.now().microsecondsSinceEpoch;
    final importJson = json
        .map((e) => {
              'name': e['name'],
              'latitude': double.parse(e['latitude']),
              'longitude': double.parse(e['longitude']),
              'createdAt': now,
              'updatedAt': now,
            })
        .toList();
    isar.writeTxn((isar) async {
      await isar.spots.importJson(importJson);
    });
  } catch (e) {
    debugPrint(e.toString());
  }
}

今回 createdAtupdatedAt がある為、importJsonRaw でダイレクトにインポート出来ない為、importJson を使用しています。
putAll で登録する場合とパフォーマンスに違いがあるか比較してみます 👀

試しに 1万件登録してみた所、手元の環境では

  • importJson だと 59msec
  • putAll だと 90msec

でした。大量のデータ登録やダイレクトにインポートできる場合は importJson の方が良さそうかもです。

既に登録されている場合はスキップ

最後に、既に登録済みの場合は初期データ登録部分をスキップするように実装します。
SharedPreferences 使ってフラグで登録済みか判断したり、登録件数に応じて判断したりと、複数パターンがありそうですが、今回は1件でも登録されていたらスキップする様に実装しました。↓

  final isar = await Isar.open(
      schemas: [SpotSchema], directory: dir.path, inspector: true);
  final exists = await isar.spots.count() > 0;
  if (!exists) {
    debugPrint('Loading spots...');
    await _loadSpots(isar);
  }

Discussion