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

Custom types #93

Closed neigaard closed 3 years ago

neigaard commented 4 years ago

I have just started using ObjectBox for Flutter and I am missing the Custom Types very very much. Is this a planned feature for the Dart/Flutter version also, and do you have any idea when (maybe you are already working on it)?

Thank you Søren

greenrobot commented 4 years ago

We are not aware of active work on this (by the community).

For the time being, maybe a workaround might work? Would it be possible to use smart getters/setters that internally do the conversion, e.g. to a string that gets persisted?

neigaard commented 4 years ago

Yes, I created fake/shadow getter and setters for those types that simply read/write from/to the real types, and this work just fine. Thank you for the tip, although I have those extra getters/setters it does work and is not to bad I think.

DPalagi commented 4 years ago

Hi ! I'm very new to objectbox, but I want to store an enum property in my Entity ; @neigaard I'm very interested by the code you've put to make this workaround please.

I think that it should be interesting for other new guys like me to have a few examples for questions like this.

Thank you 😊

neigaard commented 4 years ago

Sure, take a look at my class here:

enum HouseCategory {
  apartment,
  villa,
  terracedHouse,
  cottage
}

@Entity()
class House {
  @Id()
  int id;

  HouseCategory houseCategory;

  House({this.houseCategory});

  static String fromHouseCategory(HouseCategory houseCategory) {
    if (houseCategory == HouseCategory.apartment) {
      return "apartment";
    } else if (houseCategory == HouseCategory.villa) {
      return "villa";
    } else if (houseCategory == HouseCategory.terracedHouse) {
      return "terracedHouse";
    } else if (houseCategory == HouseCategory.cottage) {
      return "cottage";
    }
    return null;
  }

  static HouseCategory toHouseCategory(String houseCategory) {
    if (houseCategory == "apartment") {
      return HouseCategory.apartment;
    } else if (houseCategory == "villa") {
      return HouseCategory.villa;
    } else if (houseCategory == "terracedHouse") {
      return HouseCategory.terracedHouse;
    } else if (houseCategory == "cottage") {
      return HouseCategory.cottage;
    }
    return null;
  }

  /*
   * Converter setters and getters used for ObjectBox because the built in converters are not implemented in Dart yet
   */
  set houseCategoryOB(String val) {
    houseCategory = val != null ? JsonHelper.toHouseCategory(val) : null;
  }

  String get houseCategoryOB {
    return houseCategory != null ? JsonHelper.fromHouseCategory(houseCategory) : null;
  }
}

So when I use my class I just use the houseCategory property as normal, and then I added the "fake" getter and setter ending with "OB" just because it make sense to me but you can call it anything just not the same name as the real property. How this works is that ObjectBox ignores the real property because it is a unknow type, but the getter and setter is a String type to ObjectBox will call those automaticly and then in those getters and setters I do the conversion and read or write from/to the real property.

I hope it make sense and is usable.

P.S. I can not get code formatting to work :(

Best regards Søren


edited by @vaind: code formatting (added line brake and language specifier after code-block opening ```)

DPalagi commented 4 years ago

Thank you for your quick response @neigaard ! I'm gonna give it a try to integrate it into the data layer of my app 😁

For multiline code, I think that you have to put 3 times ` before code, then 3 times after it.

Edit : I had to put String get myCustomProperty on the getter in order to be interpreted as String by ObjectBox ; if someone is running into the same stuff 😉

Best regards, Damien

vaind commented 4 years ago

Nice find @neigaard, I wasn't aware custom getters already worked like that.

P.S. I've fixed the code-block in your comment

Buggaboo commented 3 years ago
enum Meh {
  bla,
  bleu
}

// @Entity()
class Type {
  // @Id()
  int id;

  Meh thing; // you can slap on @Transient (hint: please merge)
  int _shadow; // stored on db

  Type({this.thing}) {
    _shadow = this.thing.index;
  }

  // basic fugly implementation here, see pretty extensions below
  set meh (String b) {
    _shadow = Meh.values.where((v) => v.toString().endsWith(b)).toList()[0].index;
  }

  get meh {
    return Meh.values[_shadow]; 
  }

  @override
  String toString() {
    return 'Type: ${Meh.values[_shadow].toString()}';
  }
}

extension MehSteroid on Meh {
  String asStringWithoutPrefix() {
    return this.toString().substring(4);
  }

  static Meh fromString(String s) {
    for (var v in Meh.values) {
      if (v.asStringWithoutPrefix() == s) {
        return v;
      }
    }

    return null;
  }
}

/// if you don't want to pollute your class with accessors
extension Steroid on Type {

  // same setter, but elsewhere
  set ahem (String b) {
    _shadow = MehSteroid.fromString(b)?.index;
  }
}

void main() {
  print(Type(thing:Meh.bleu)..meh = 'bla');
  print(Type(thing:Meh.bla)..ahem = 'bleu');
}

My two cents, on extensions and enums and shadowing and converters. If you're lazy like me, you can create your own @EnumSteroid annotation to generate the extension code using code_gen.

vaind commented 3 years ago

This probably won't make it to 1.0 but we should evaluate whether any breaking API changes would be necessary.

vaind commented 3 years ago

Looking at this again after I've just had a look into storing final fields...

To me, I don't think there's anything to be done (in the generator code... docs definitely need an update) to actually support custom types, dart getters and setters work like a charm as some of you already found out.

However, one I wanted to share about storing enums - it's not very safe to store the string representation or an index. Instead, you should define your own map (e.g. use a switch case) to a fixed numbers so that your code doesn't break after you rename an enum, shuffle them around or add a new one. ObjectBox for Java docs have some hints about this: https://docs.objectbox.io/advanced/custom-types#how-to-convert-enums-correctly

vaind commented 3 years ago

added an example to docs: https://docs.objectbox.io/advanced/custom-types#convert-annotation-and-property-converter