0

In my Flutter project, I use Isar for my local database (version 3.1.0+1).

In my app, I have groups and tags, with a 1-n relationship (one group can have zero, one or multiple tags).

I want to add a group with its tags in a single transaction, so here is what I did:

  late GroupsDAO _groupsDAO;
  
  @override
  void initState()
  {
    super.initState();
    _groupsDAO = GroupsDAO(openDB());
  }
  
  void _addItemAndSubItems()
  {
    final group = Group()
      ..label = "My group"
      ..tags.add(Tag()..label = "My tag");
    print("tags : ${group.tags.length}");
    _groupsDAO.addOrUpdate(group);
  }

  @override
  Widget build(BuildContext context)
  {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text("Isar 1-n test"),
      ),
      body: Container(),
      floatingActionButton: FloatingActionButton(
        onPressed: _addItemAndSubItems,
        child: const Icon(Icons.arrow_forward),
      ),
    );
  }

For your information, here are the entities:

part 'group.g.dart';

@Collection()
class Group
{
  Id id = Isar.autoIncrement;

  @Index(unique: true)
  late String label;
  
  final tags = IsarLinks<Tag>(); 
}
part 'tag.g.dart';

@Collection()
class Tag
{
  Id id = Isar.autoIncrement;
  late String label;
  
  @Backlink(to: "tags")
  final group = IsarLink<Group>();
}

And here is the GroupsDAO class:

class GroupsDAO
{
  final Future<Isar> db;

  GroupsDAO(this.db);
  
  Future<void> addOrUpdate(Group group) async
  {
    final isar = await db;
    await isar.writeTxn(() async {
      await isar.tags.putAll(group.tags.toList());
      await isar.groups.put(group);
      await group.tags.save();
    });
  }
}

And the openDB() function:

Future<Isar> openDB() async
{
  if (Isar.instanceNames.isEmpty)
  {
    final dbFolder = await getApplicationDocumentsDirectory();
    return await Isar.open(
      [TagSchema, GroupSchema],
      inspector: true,
      directory: dbFolder.path,
    );
  }
  return Future.value(Isar.getInstance());
}

And here is the full reproductible example.

But when I try to add a group, I get the following error, even though I believe is what I already did:

Unhandled Exception: IsarError: Object "Instance of 'Tag'" has no id and can therefore not be linked. Make sure to .put() objects before you use them in links.

Also, for some reason, the print("tags : ${group.tags.length}"); line always print tags : 0, and I don't understand why.

I think I missed something simple but I don't know what.

UPDATE

I somehow managed to add my group and my tag by doing the following:

final id = await _groupsDAO.addTag(Tag()..label = "My tag");
final group = Group()
  ..label = "My group"
  ..tags.add(Tag()..id = id..label = "My tag");
_groupsDAO.addOrUpdate(group);

and the addTag() function:

Future<int> addTag(Tag tag) async
{
  final isar = await db;
  return await isar.writeTxn(() async {
    return await isar.tags.put(tag);
  });
}

and the addOrUpdate() function:

Future<void> addOrUpdate(Group group) async
{
  final isar = await db;
  await isar.writeTxn(() async {
    await isar.groups.put(group);
    await group.tags.save();
  });
}

But this is not a satisfying solution since I don't take advantage of any transaction: if I get an error when adding a group, I still get an orphaned tag, which is not what I want.

So how can I add a group with its tags in a transaction using Isar?

Thanks for your help.

matteoh
  • 2,810
  • 2
  • 29
  • 54

0 Answers0