isar / hive

Lightweight and blazing fast key-value database written in pure Dart.
Apache License 2.0
4.11k stars 410 forks source link

I am afraid of Hive Code Generator doesn't see generic types #273

Open machinescream opened 4 years ago

machinescream commented 4 years ago

Screenshot 2020-04-05 at 16 13 02 Screenshot 2020-04-05 at 16 13 07

MarcelGarus commented 4 years ago

Sadly, it does not, but it's being worked on. For now, you can manually write a class that implements TypeAdapter<T> and has an id field. Then you can register it for multiple IDs:

Hive
  ..registerAdapter(MyAdapter<int>(id: 10)
  ..registerAdapter(MyAdapter<double>(id: 11)
BasedMusa commented 4 years ago

Hi @marcelgarus @leisim , I was working on a desktop app and I need to save slightly complex data which also includes Generics, so I'd really appreciate if you could guide me on how to save my data in Hive. Other than the error, it'd be really nice of you to give me a few pointers on what I might be doing wrong here.

Note: The object I want to save is CodeTemplate.

Error

Launching lib/main.dart on macOS in debug mode...
Building macOS application...

Compiler message:
lib/utils/models.g.dart:131:34: Error: 'T' isn't a type.
      defaultValue: fields[3] as T,
                                 ^
lib/utils/models.g.dart:135:30: Error: 'T' isn't a type.
      ..value = fields[2] as T
                             ^

My Data Structure


@HiveType(typeId: 2)
class CodeTemplate extends HiveObject {
  @HiveField(1)
  String name;
  @HiveField(2)
  String language;
  @HiveField(3)
  Map<int, TemplateSequenceOption> sequence;

  CodeTemplate({
    @required this.name,
    @required this.language,
    @required this.sequence,
});
}

@HiveType(typeId: 3)
class TemplateSequenceOption extends HiveObject {
  @HiveField(1)
  int id;
  @HiveField(2)
  String name;
  @HiveField(3)
  Color color;
  @HiveField(4)
  IconData iconData;
  @HiveField(5)
  TemplateSequenceOptionType type;
  @HiveField(6)
  Map<String, TemplateSequenceProperty> properties;

  TemplateSequenceOption(this.id, this.name, this.color, this.iconData,
      this.type, this.properties);
}

@HiveType(typeId: 4)
class TemplateSequenceProperty<T> extends HiveObject {
  @HiveField(1)
  int id;
  @HiveField(2)
  T value;
  @HiveField(3)
  T defaultValue;
  @HiveField(4)
  String hintText;
  @HiveField(5)
  Type dataType;
  @HiveField(6)
  bool optional;

  TemplateSequenceProperty(this.id,
      {this.defaultValue, this.hintText, this.optional = true})
      : this.dataType = T;
}

My Data Saving Code


      await Hive.initFlutter(); // Called once only
      Hive.registerAdapter(CodeTemplateAdapter()); // Called once only
      await Hive.openBox<CodeTemplate>(codeTemplatesBox); // Called once only

      CodeTemplate template = CodeTemplate(
        name: _nameController.text,
        language: _selectedLanguage,
        sequence: _sequence,
      );

      Box<CodeTemplate> _codeTemplatesBox = Hive.box(codeTemplatesBox);
      await _codeTemplatesBox.add(template);

Models.G.Dart

...

class TemplateSequencePropertyAdapter
    extends TypeAdapter<TemplateSequenceProperty> {
  @override
  final typeId = 4;

  @override
  TemplateSequenceProperty read(BinaryReader reader) {
    var numOfFields = reader.readByte();
    var fields = <int, dynamic>{
      for (var i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
    };
    return TemplateSequenceProperty(
      fields[1] as int,
      defaultValue: fields[3] as T, // ---------- Line 131
      hintText: fields[4] as String,
      optional: fields[6] as bool,
    )
      ..value = fields[2] as T // ---------- Line 135
      ..dataType = fields[5] as Type;
  }

  @override
  void write(BinaryWriter writer, TemplateSequenceProperty obj) {
    writer
      ..writeByte(6)
      ..writeByte(1)
      ..write(obj.id)
      ..writeByte(2)
      ..write(obj.value)
      ..writeByte(3)
      ..write(obj.defaultValue)
      ..writeByte(4)
      ..write(obj.hintText)
      ..writeByte(5)
      ..write(obj.dataType)
      ..writeByte(6)
      ..write(obj.optional);
  }
}
MarcelGarus commented 4 years ago

Hive doesn't support generics out-of-the box. Yet. That means, generating code for generic classes doesn't work yet. Which in turn means, you need to write your own custom type adapter.

Here's a modified version of the automatically generated TemplateSequencePropertyAdapter, which supports generics. You can copy it into your project (I only changed the first 6 lines):

class TemplateSequencePropertyAdapter<T>
    extends TypeAdapter<TemplateSequenceProperty<T>> {
  TemplateSequencePropertyAdapter({@required this.typeId}) : assert(typeId != null);

  @override
  final int typeId;

  @override
  TemplateSequenceProperty read(BinaryReader reader) {
    var numOfFields = reader.readByte();
    var fields = <int, dynamic>{
      for (var i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
    };
    return TemplateSequenceProperty(
      fields[1] as int,
      defaultValue: fields[3] as T,
      hintText: fields[4] as String,
      optional: fields[6] as bool,
    )
      ..value = fields[2] as T
      ..dataType = fields[5] as Type;
  }

  @override
  void write(BinaryWriter writer, TemplateSequenceProperty obj) {
    writer
      ..writeByte(6)
      ..writeByte(1)
      ..write(obj.id)
      ..writeByte(2)
      ..write(obj.value)
      ..writeByte(3)
      ..write(obj.defaultValue)
      ..writeByte(4)
      ..write(obj.hintText)
      ..writeByte(5)
      ..write(obj.dataType)
      ..writeByte(6)
      ..write(obj.optional);
  }
}

You can then register this adapter for all the generic types you want to support:

await Hive.initFlutter();
Hive
  ..registerAdapter(CodeTemplateAdapter())
  ..registerAdapter(TemplateSequenceOptionAdapter())
  ..registerAdapter(TemplateSequencePropertyAdapter<int>(typeId: 9))
  ..registerAdapter(TemplateSequencePropertyAdapter<String>(typeId: 10))
  ..registerAdapter(TemplateSequencePropertyAdapter<double>(typeId: 11));
await Hive.openBox<CodeTemplate>(codeTemplatesBox);

Finally, you can use this hand-written adapter in your code:

CodeTemplate template = CodeTemplate(
  name: _nameController.text,
  language: _selectedLanguage,
  sequence: _sequence,
);

Box<CodeTemplate> _codeTemplatesBox = Hive.box(codeTemplatesBox);
await _codeTemplatesBox.add(template);

I know this is loads of boilerplate, but I suppose type-safety comes built-in with Hive 2.0.

BasedMusa commented 4 years ago

@marcelgarus Thanks for the response, one last thing, since we've manually written the adapter, I should remove the @HiveType and @HiveField properties from CodeTemplateProperty still extend it from a HiveObject. Right?

And can the generic be of another HiveObject type? For example, I need to use this in my code:

TemplateSequenceProperty<CodeSnippet> property = TemplateSequenceProperty<CodeSnippet>();

//Code Snippet extends from a HiveObject and has a TypeAdapter with typeId: 1
MarcelGarus commented 4 years ago

Yes exactly, the annotations should be removed, but you should still extend HiveObject if you want save() functions etc.

It shouldn't be a problem that the generic type also extends HiveObject.

yannisoo commented 2 years ago

@MarcelGarus Hi any idea if genericType has been implemented ever since ? Thanks in advance !

MarcelGarus commented 2 years ago

AFAIK, that's not planned for the future. Hive creator @leisim is working on the new database Isar, so Hive is somewhat in a feature-freeze.

definitelyme commented 6 months ago

Hive doesn't support generics out-of-the box. Yet. That means, generating code for generic classes doesn't work yet. Which in turn means, you need to write your own custom type adapter.

Here's a modified version of the automatically generated TemplateSequencePropertyAdapter, which supports generics. You can copy it into your project (I only changed the first 6 lines):

class TemplateSequencePropertyAdapter<T>
    extends TypeAdapter<TemplateSequenceProperty<T>> {
  TemplateSequencePropertyAdapter({@required this.typeId}) : assert(typeId != null);

  @override
  final int typeId;

  @override
  TemplateSequenceProperty read(BinaryReader reader) {
    var numOfFields = reader.readByte();
    var fields = <int, dynamic>{
      for (var i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
    };
    return TemplateSequenceProperty(
      fields[1] as int,
      defaultValue: fields[3] as T,
      hintText: fields[4] as String,
      optional: fields[6] as bool,
    )
      ..value = fields[2] as T
      ..dataType = fields[5] as Type;
  }

  @override
  void write(BinaryWriter writer, TemplateSequenceProperty obj) {
    writer
      ..writeByte(6)
      ..writeByte(1)
      ..write(obj.id)
      ..writeByte(2)
      ..write(obj.value)
      ..writeByte(3)
      ..write(obj.defaultValue)
      ..writeByte(4)
      ..write(obj.hintText)
      ..writeByte(5)
      ..write(obj.dataType)
      ..writeByte(6)
      ..write(obj.optional);
  }
}

You can then register this adapter for all the generic types you want to support:

await Hive.initFlutter();
Hive
  ..registerAdapter(CodeTemplateAdapter())
  ..registerAdapter(TemplateSequenceOptionAdapter())
  ..registerAdapter(TemplateSequencePropertyAdapter<int>(typeId: 9))
  ..registerAdapter(TemplateSequencePropertyAdapter<String>(typeId: 10))
  ..registerAdapter(TemplateSequencePropertyAdapter<double>(typeId: 11));
await Hive.openBox<CodeTemplate>(codeTemplatesBox);

Finally, you can use this hand-written adapter in your code:

CodeTemplate template = CodeTemplate(
  name: _nameController.text,
  language: _selectedLanguage,
  sequence: _sequence,
);

Box<CodeTemplate> _codeTemplatesBox = Hive.box(codeTemplatesBox);
await _codeTemplatesBox.add(template);

I know this is loads of boilerplate, but I suppose type-safety comes built-in with Hive 2.0.

Still works. Thanks!