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

Add support for property converters #286

Open edwardaux opened 2 years ago

edwardaux commented 2 years ago

Hi, I'm a relatively new ObjectBox user, so firstly let me say thanks for what looks like an awesome library 💪

I'm investigating how I might go about encoding custom types in my current application. I've seen this (closed) issue about supporting custom types https://github.com/objectbox/objectbox-dart/issues/93 and I've also seen the example Dart code in https://docs.objectbox.io/advanced/custom-types#convert-annotation-and-property-converter.

The problem is that the pattern suggested here isn't really scalable. It means a bunch of boilerplate to create a backing variable (which will be ignored by ObjectBox) and then creating a getter/setter that wraps around that variable. When I'm interacting with this object, it means there's another "variable" (dbXXX) that could be interacted with (but should never be). Plus, all this boilerplate needs to be copied/pasted into every class that happens to embed the custom type we're trying to convert.

I guess what I'm seeking is something like the Kotlin/Java @Convert annotation so that my classes are nice and clean, and I've got reusable converter classes. Is this something being considered?

Apologies if if the above sounds like it is negative... I don't intend it that way. Thanks again for an amazing library.

edwardaux commented 2 years ago

FWIW, it also makes it painful to use final fields. eg. consider the following class:

@Entity()
class Blah {
  int id;
  final Map<String, String> map;

  Blah(this.id, this.map);

  String get dbMap {
    return ''; // conversion code
  }
  set dbMap(String v) {
    // conversion code
  }
}

Because ObjectBox doesn't know how to convert a Map, it has to use the default constructor, so the generated code for this looks like the following, which doesn't compile because there's no empty constructor.

objectFromFB: (Store store, ByteData fbData) {
  final buffer = fb.BufferContext(fbData);
  final rootOffset = buffer.derefObject(0);

  final object = Blah()
    ..dbMap =
        const fb.StringReader().vTableGet(buffer, rootOffset, 6, '');

Plus in set dbMap(), I can't replace the value of the underlying map variable (well, technically in this example I can update the contents of a Dart Map in-place... however, there are loads of objects that you can't update the contents and need to replace the whole object. eg. Uri)

Another (minor) nitpick is that now my code generation has a bunch of warnings it about Map class that it is ignoring. I find that when we get into the habit of ignoring warnings like this, we also tend to start ignoring warnings that actually matter. I've found that I can at least suppress that warning by using Transient(), however, again that's a bit more boilerplate.

vaind commented 2 years ago

You've promoted your case very well, thanks for going to the extent and no, absolutely not taken negatively.

I can definitely see your point and I don't think there's anything preventing us to support both in-class converters via getters/setters and a custom class (or an extension?) :+1:

James-Aidoo commented 2 years ago

Any news on this?

orestesgaolin commented 2 years ago

It would be definitely much more convenient if we would have some kind of property annotations that would allow for automatic conversion, similarly to Java and Kotlin @Convert annotations.

Currently it's necessary to create backing fields, getter, and potentially setter like this (although in immutable examples I'd do it with copyWith):

enum Role {
  unknown,
  author,
  admin
}

@Entity()
class User {
  User(this.id, this.roleEncoded);

  final int id;

  // The Role type is not supported by ObjectBox.
  Role get role => RoleConverter.decodeFromString(roleEncoded);

  final String roleEncoded;
}

class RoleConverter {
  static Role decodeFromString(String role) {
    ...
  }
}

I would be super happy to see API similar to this instead:

@Entity()
class User {
  User(this.id, this.role);

  final int id;

  @RoleConverter()
  final Role? role;
}

class RoleConverter extends ObjectboxConverter {
...
}

or

@Entity()
class User {
  User(this.id, this.role);

  final int id;

  @Property(fromDb: RoleConverter.fromDb, toDb: RoleConverter.toDb) // or similar
  final Role? role;
}

class RoleConverter {
...
}
noob8boi commented 3 months ago

Hello any progress on this?

greenrobot-team commented 3 months ago

@noob8boi We'll share updates when there are some. If you are interested in this, it helps us more to prioritize this by using thumbs up on the first comment!