objectbox / objectbox-dart

Flutter database for super-fast Dart object persistence
https://docs.objectbox.io/getting-started
Apache License 2.0
927 stars 115 forks source link

Freezed (immutable) support with 0 id fails due to assignment to final id #307

Open ilikerobots opened 2 years ago

ilikerobots commented 2 years ago

In #229, the example supporting freezed entities uses assignable ids. Since freezed demands entities to be immutable, attempts to use value 0 ids fail in https://github.com/objectbox/objectbox-dart/blob/e587529f3a32ce7769e9a53a24ea4afafdd6d970/objectbox/lib/src/native/box.dart#L222 with error

Unhandled Exception: Invalid argument(s): Field Model.id is read-only (final or getter-only) and it was declared to be self-assigned. However, the currently inserted object (.id=0) doesn't match the inserted ID (ID 1). You must assign an ID before calling [box.put()].

Is it possible to use non-assignable ids with immutable entities currently? If not, would it be beneficial to make it so, e.g. by modifying the code above to use a copyWith or some other mechanism to set newly assigned ids?

vaind commented 2 years ago

Not sure how we could do this. The contract of box.put() (and putMany()) is that it sets the ID on the given object, if it was previously zero. This is especially important with relations (and relation cycles) so that when the related data is being put, the existing objects aren't reinserted always as new ones (that would result in an infinite recursion).

ilikerobots commented 2 years ago

Ok, thanks. If I understand correctly,then, a project that uses objectbox and immutable models is required to fully manage IDs independently, right?

tomwyr commented 2 years ago

Do you think it would be possible to either use models that are mutable or immutable and they declare a function that transforms given instance with zero id to an instance with id generated by ObjectBox (which is then added to database)? So something like:

@Entity()
@freezed
class Book with _$Book {
  factory Book({
    @Id(assignBy: getBookWithId) @Default(0) int id,
  }) = _Book;
}

Book getBookWithId(Book book, int id) => book.copyWith(id: id);
lampian commented 2 years ago

Using Equatable requires entity class to be immutable. This then require the Id field to be final, which currently is not supported. Please consider providing a way to update the entity as a whole with all fields as final. The solution suggested by @tomwyr is pointing in the right direction - not sure what side effects there would be.

vaind commented 2 years ago

@tomwyr - unfortunately that still won't satisfy the current "contract" of box.put() (or putMany()) which says it updates the given object. Besides this being possibly relied upon in client apps, it's also used when working with relations, requiring the relation target object instance to be updated with the inserted ID. Getting around this may require some more thought...

Anyone interested in this, please upvote the original issue (at the top) so the interest can be tracked when prioritizing development.

rafuck commented 2 years ago

https://github.com/objectbox/objectbox-dart/pull/393

raj457036 commented 2 months ago

This works btw! ( note: this will make your properties mutable else everything will be same )

@Freezed(addImplicitFinal: false)
sealed class YourClass with _$YourClass {
    @Entity(realClass: YourClass)
    factory YourClass({
        @Default(0) @Id() int localId,
        @Unique() String? id,
    }) = _YourClass;
}