iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
📲

[Flutter] Seeding Initial Data with Isar

に公開

Goal

I want to pre-register necessary initial data when the app is first launched!

  • Note: I'm assuming that data will be loaded from a JSON file prepared within the app, rather than via an API.

Method Tried

isar/samples: Sample apps using Isar
In the repository above, since the sample was loading and registering data from assets/quotes.json when no initial data existed, I'll try to follow that approach.

Environment Setup

In this case, I will use fvm and work in the following environment:

macOS Monterey Version 12.1 Apple M1
iOS 15.0 iPhone 13 Simulator
Flutter SDK version: 2.10.3
Dart SDK version: 2.16.1

First, create an empty Flutter project and add the following Isar-related packages:

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

Creating the Spot Schema

In this step, we will create the following Spot schema and register its initial data.
(I'll proceed with a plausible scenario where "famous spots around the world need to be registered in advance.")

import 'package:isar/isar.dart';

part 'spot.g.dart';

@Collection()
class Spot {
  int? id;

  @Index()
  late String name;

  late double longitude;

  late double latitude;

  late DateTime createdAt;

  late DateTime updatedAt;
}

Run build_runner to generate spot.g.dart.

$ flutter pub run build_runner build

Initializing 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());
}
  • Note: I have set inspector: true to allow referencing it with the Isar Inspector.

Loading asset JSON and writing to 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); // ← Added

  runApp(const MyApp());
}

Regarding _loadSpots, it is a slightly customized version of this from the Isar samples, and the logic is the same.

The content of assets/spots.json is a simple structure:

[
  {
    "name": "Athens",
    "longitude": "23.718989",
    "latitude": "37.973620"
  },
  {
    "name": "Istanbul",
    "longitude": "29.006069",
    "latitude": "41.065960"
  },
  {
    "name": "Vienna",
    "longitude": "16.364571",
    "latitude": "48.201841"
  }
]

Add assets/spots.json to your pubspec.yaml.

flutter:
  assets:
    - assets/spots.json

Now, let's launch the app and check if it has been registered via the Isar Inspector.

As shown above, the data set in the JSON has been successfully registered! ✨

Using importJson provided by Isar

Actually, Isar provides importJson, importJsonRaw, importJsonSync, and importJsonRawSync which look like they can be used to import from JSON files.
https://github.com/isar/isar/blob/cb047f18c9f7711de4bc41832327a4e1c1b4d934/packages/isar/lib/src/native/isar_collection_impl.dart#L383

Let's try this too 👀
Modify the previous _loadSpots to the following:

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());
  }
}

In this case, since we have createdAt and updatedAt, we cannot import directly using importJsonRaw, so we use importJson.
Let's compare if there is a performance difference between registering with putAll and this method 👀

When I tried registering 10,000 items in my environment:

  • 59msec with importJson
  • 90msec with putAll

importJson might be better when registering large amounts of data or when you can import directly.

Skipping if already registered

Finally, we will implement it so that if the data is already registered, the initial data loading part is skipped.
There are likely several patterns for this, such as using SharedPreferences to determine if it's already registered using a flag or checking based on the record count. In this case, I implemented it to skip if at least one record exists. ↓

  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