diff --git a/hive/CHANGELOG.md b/hive/CHANGELOG.md index 7c98f2a3..331457e5 100644 --- a/hive/CHANGELOG.md +++ b/hive/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.8.0 + +- Adds `GenerateAdapters` annotation and relevant documentation + ## 2.7.0+1 - Adds a storage benchmark to compare Hive CE with Hive v4 diff --git a/hive/README.md b/hive/README.md index 882cca31..624faad9 100644 --- a/hive/README.md +++ b/hive/README.md @@ -27,6 +27,9 @@ Hive CE is a spiritual continuation of Hive v2 with the following new features: - Support for constructor parameter defaults - Freezed support - Support for generating adapters with classes that use named imports +- Automatic type adapter generation using the `GenerateAdapters` annotation + - No more manually adding annotations to every type and field + - Generate adapters for classes outside the current package ## Hive CE (v2) vs Hive v4 (Isar) @@ -187,19 +190,34 @@ Hive not only supports primitives, lists, and maps but also any Dart object you ```dart import 'package:hive_ce/hive.dart'; -@HiveType(typeId: 0) class Person extends HiveObject { Person({required this.name, required this.age}); - @HiveField(0) String name; - - @HiveField(1) int age; } ``` +### Create a `GenerateAdapters` annotation + +Usually this is placed in `lib/hive/hive_adapters.dart` + + + +```dart +import 'package:hive_ce/hive.dart'; +import 'person.dart'; + +part 'hive_adapters.g.dart'; + +@GenerateAdapters([AdapterSpec()]) +// Annotations must be on some element +// ignore: unused_element +void _() {} + +``` + ### Update `pubspec.yaml` ```yaml @@ -214,10 +232,20 @@ dev_dependencies: dart pub run build_runner build --delete-conflicting-outputs ``` -This will generate all of your `TypeAdapter`s as well as a Hive extension to register them all in one go +This will generate the following: + +- TypeAdapters for the specified AdapterSpecs +- TypeAdapters for all explicitly defined HiveTypes +- A `hive_adapters.g.dart` file containing all adapters generated from the `GenerateAdapters` annotation +- A `hive_adapters.g.yaml` file +- A `hive_registrar.g.dart` file containing an extension method to register all generated adapters + +All of the generated files should be checked into version control. These files are explained in more detail below. ### Use the Hive registrar +The Hive Registrar allows you to register all generated TypeAdapters in one call + ```dart import 'dart:io'; import 'package:hive_ce/hive.dart'; @@ -256,6 +284,38 @@ void example() async { ``` +### About `hive_adapters.g.yaml` + +The Hive schema is a generated yaml file that contains the information necessary to incrementally update the generated TypeAdapters as your model classes evolve. + +Some migrations may require manual modifications to the Hive schema file. One example is class/field renaming. Without manual intervention, the generator will see both an added and removed class/field. To resolve this, manually rename the class/field in the schema. + +### Migrating to `GenerateAdapters` + +If you already have model classes with `HiveType` and `HiveField` annotations, you can take the following steps to migrate to the new `GenerateAdapters` annotation: + +1. Convert all default values to constructor parameter defaults +2. Add the following to your `build.yaml` file: + +```yaml +targets: + $default: + builders: + hive_ce_generator|hive_schema_migrator: + enabled: true +``` + +3. Run the `build_runner`. This will generate `lib/hive/hive_adapters.dart` and `lib/hive/hive_adapters.g.yaml`. +4. Revert the `build.yaml` changes +5. Remove all explicit `HiveType` and `HiveField` annotations from your model classes +6. Run the `build_runner` again + +### Explicitly defining HiveTypes + +The old method of defining HiveTypes is still supported, but should be unnecessary now that Hive CE supports constructor parameter defaults. If you have a use-case that `GenerateAdapters` does not support, please [create an issue on GitHub](https://github.com/IO-Design-Team/hive_ce/issues/new). + +Unfortunately it is not possible for `GenerateAdapters` to handle private fields. You can use `@protected` instead if necessary. + ## Add fields to objects When adding a new non-nullable field to an existing object, you need to specify a default value to ensure compatibility with existing data. @@ -267,14 +327,10 @@ For example, consider an existing database with a `Person` object: ```dart import 'package:hive_ce/hive.dart'; -@HiveType(typeId: 0) class Person extends HiveObject { Person({required this.name, required this.age}); - @HiveField(0) String name; - - @HiveField(1) int age; } @@ -287,31 +343,16 @@ If you want to add a `balance` field, you must specify a default value or else r ```dart import 'package:hive_ce/hive.dart'; -@HiveType(typeId: 0) class Person extends HiveObject { Person({required this.name, required this.age, this.balance = 0}); - @HiveField(0) String name; - - @HiveField(1) int age; - - @HiveField(2) double balance; } ``` -Or specify it in the `HiveField` annotation: - -```dart -@HiveField(2, defaultValue: 0) -int balance; -``` - -Alternatively, you can write custom migration code to handle the transition. - After modifying the model, remember to run `build_runner` to regenerate the TypeAdapters ## Hive ❤️ Flutter diff --git a/hive/example/lib/freezed.dart b/hive/example/lib/freezed.dart index 8c730fd4..08e24fd2 100644 --- a/hive/example/lib/freezed.dart +++ b/hive/example/lib/freezed.dart @@ -1,15 +1,12 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:hive_ce/hive.dart'; part 'freezed.freezed.dart'; -part 'freezed.g.dart'; @freezed -@HiveType(typeId: 100) class FreezedPerson with _$FreezedPerson { const factory FreezedPerson({ - @HiveField(0) required String firstName, - @HiveField(1) required String lastName, - @HiveField(2) required int age, + required String firstName, + required String lastName, + required int age, }) = _FreezedPerson; } diff --git a/hive/example/lib/freezed.freezed.dart b/hive/example/lib/freezed.freezed.dart index 4e50080b..3f7e9aae 100644 --- a/hive/example/lib/freezed.freezed.dart +++ b/hive/example/lib/freezed.freezed.dart @@ -16,11 +16,8 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$FreezedPerson { - @HiveField(0) String get firstName => throw _privateConstructorUsedError; - @HiveField(1) String get lastName => throw _privateConstructorUsedError; - @HiveField(2) int get age => throw _privateConstructorUsedError; /// Create a copy of FreezedPerson @@ -36,10 +33,7 @@ abstract class $FreezedPersonCopyWith<$Res> { FreezedPerson value, $Res Function(FreezedPerson) then) = _$FreezedPersonCopyWithImpl<$Res, FreezedPerson>; @useResult - $Res call( - {@HiveField(0) String firstName, - @HiveField(1) String lastName, - @HiveField(2) int age}); + $Res call({String firstName, String lastName, int age}); } /// @nodoc @@ -86,10 +80,7 @@ abstract class _$$FreezedPersonImplCopyWith<$Res> __$$FreezedPersonImplCopyWithImpl<$Res>; @override @useResult - $Res call( - {@HiveField(0) String firstName, - @HiveField(1) String lastName, - @HiveField(2) int age}); + $Res call({String firstName, String lastName, int age}); } /// @nodoc @@ -130,18 +121,13 @@ class __$$FreezedPersonImplCopyWithImpl<$Res> class _$FreezedPersonImpl implements _FreezedPerson { const _$FreezedPersonImpl( - {@HiveField(0) required this.firstName, - @HiveField(1) required this.lastName, - @HiveField(2) required this.age}); + {required this.firstName, required this.lastName, required this.age}); @override - @HiveField(0) final String firstName; @override - @HiveField(1) final String lastName; @override - @HiveField(2) final int age; @override @@ -175,18 +161,15 @@ class _$FreezedPersonImpl implements _FreezedPerson { abstract class _FreezedPerson implements FreezedPerson { const factory _FreezedPerson( - {@HiveField(0) required final String firstName, - @HiveField(1) required final String lastName, - @HiveField(2) required final int age}) = _$FreezedPersonImpl; + {required final String firstName, + required final String lastName, + required final int age}) = _$FreezedPersonImpl; @override - @HiveField(0) String get firstName; @override - @HiveField(1) String get lastName; @override - @HiveField(2) int get age; /// Create a copy of FreezedPerson diff --git a/hive/example/lib/hive/hive_adapters.dart b/hive/example/lib/hive/hive_adapters.dart new file mode 100644 index 00000000..a4390873 --- /dev/null +++ b/hive/example/lib/hive/hive_adapters.dart @@ -0,0 +1,13 @@ +import 'package:example/freezed.dart'; +import 'package:example/main.dart'; +import 'package:hive_ce/hive.dart'; + +part 'hive_adapters.g.dart'; + +@GenerateAdapters([ + AdapterSpec(), + AdapterSpec(), +]) +// This is for code generation +// ignore: unused_element +void _() {} diff --git a/hive/example/lib/freezed.g.dart b/hive/example/lib/hive/hive_adapters.g.dart similarity index 53% rename from hive/example/lib/freezed.g.dart rename to hive/example/lib/hive/hive_adapters.g.dart index 354a1cee..52d14c03 100644 --- a/hive/example/lib/freezed.g.dart +++ b/hive/example/lib/hive/hive_adapters.g.dart @@ -1,14 +1,54 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'freezed.dart'; +part of 'hive_adapters.dart'; // ************************************************************************** -// TypeAdapterGenerator +// AdaptersGenerator // ************************************************************************** +class PersonAdapter extends TypeAdapter { + @override + final int typeId = 0; + + @override + Person read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return Person( + name: fields[0] as String, + age: (fields[1] as num).toInt(), + friends: (fields[2] as List).cast(), + ); + } + + @override + void write(BinaryWriter writer, Person obj) { + writer + ..writeByte(3) + ..writeByte(0) + ..write(obj.name) + ..writeByte(1) + ..write(obj.age) + ..writeByte(2) + ..write(obj.friends); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PersonAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} + class FreezedPersonAdapter extends TypeAdapter { @override - final int typeId = 100; + final int typeId = 1; @override FreezedPerson read(BinaryReader reader) { diff --git a/hive/example/lib/hive/hive_adapters.g.yaml b/hive/example/lib/hive/hive_adapters.g.yaml new file mode 100644 index 00000000..71cfb74c --- /dev/null +++ b/hive/example/lib/hive/hive_adapters.g.yaml @@ -0,0 +1,25 @@ +# Generated by Hive CE +# Manual modifications may be necessary for certain migrations +# Check in to version control +nextTypeId: 2 +types: + Person: + typeId: 0 + nextIndex: 3 + fields: + name: + index: 0 + age: + index: 1 + friends: + index: 2 + FreezedPerson: + typeId: 1 + nextIndex: 3 + fields: + firstName: + index: 0 + lastName: + index: 1 + age: + index: 2 diff --git a/hive/example/lib/hive_registrar.g.dart b/hive/example/lib/hive/hive_registrar.g.dart similarity index 78% rename from hive/example/lib/hive_registrar.g.dart rename to hive/example/lib/hive/hive_registrar.g.dart index 0efb9d27..37cdbcb2 100644 --- a/hive/example/lib/hive_registrar.g.dart +++ b/hive/example/lib/hive/hive_registrar.g.dart @@ -3,8 +3,7 @@ // Check in to version control import 'package:hive_ce/hive.dart'; -import 'package:example/freezed.dart'; -import 'package:example/main.dart'; +import 'package:example/hive/hive_adapters.dart'; extension HiveRegistrar on HiveInterface { void registerAdapters() { diff --git a/hive/example/lib/main.dart b/hive/example/lib/main.dart index 42498a72..c8783f20 100644 --- a/hive/example/lib/main.dart +++ b/hive/example/lib/main.dart @@ -1,21 +1,13 @@ import 'dart:io'; -import 'package:example/hive_registrar.g.dart'; +import 'package:example/hive/hive_registrar.g.dart'; import 'package:hive_ce/hive.dart'; -part 'main.g.dart'; - -@HiveType(typeId: 1) class Person { Person({required this.name, required this.age, required this.friends}); - @HiveField(0) String name; - - @HiveField(1) int age; - - @HiveField(2) List friends; @override diff --git a/hive/example/lib/main.g.dart b/hive/example/lib/main.g.dart deleted file mode 100644 index 73933e58..00000000 --- a/hive/example/lib/main.g.dart +++ /dev/null @@ -1,47 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'main.dart'; - -// ************************************************************************** -// TypeAdapterGenerator -// ************************************************************************** - -class PersonAdapter extends TypeAdapter { - @override - final int typeId = 1; - - @override - Person read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return Person( - name: fields[0] as String, - age: (fields[1] as num).toInt(), - friends: (fields[2] as List).cast(), - ); - } - - @override - void write(BinaryWriter writer, Person obj) { - writer - ..writeByte(3) - ..writeByte(0) - ..write(obj.name) - ..writeByte(1) - ..write(obj.age) - ..writeByte(2) - ..write(obj.friends); - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is PersonAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} diff --git a/hive/example/pubspec.yaml b/hive/example/pubspec.yaml index c1d1ba25..c1a709bc 100644 --- a/hive/example/pubspec.yaml +++ b/hive/example/pubspec.yaml @@ -17,4 +17,9 @@ dependency_overrides: hive_ce: path: ../ hive_ce_generator: - path: ../../hive_generator + # TODO: Revert + # path: ../../hive_generator + git: + url: https://github.com/IO-Design-Team/hive_ce + ref: 4f06756e5eee0b027f2e9d623e8136620309afcb + path: hive_generator diff --git a/hive/lib/hive.dart b/hive/lib/hive.dart index f88a6883..47036746 100644 --- a/hive/lib/hive.dart +++ b/hive/lib/hive.dart @@ -10,6 +10,7 @@ export 'src/box_collection/box_collection_stub.dart' if (dart.library.io) 'package:hive_ce/src/box_collection/box_collection.dart'; export 'src/object/hive_object.dart' show HiveObject, HiveObjectMixin; +export 'src/annotations/generate_adapters.dart'; export 'src/annotations/hive_field.dart'; export 'src/annotations/hive_type.dart'; export 'src/binary/binary_reader.dart'; diff --git a/hive/lib/src/annotations/generate_adapters.dart b/hive/lib/src/annotations/generate_adapters.dart new file mode 100644 index 00000000..e797ef41 --- /dev/null +++ b/hive/lib/src/annotations/generate_adapters.dart @@ -0,0 +1,21 @@ +/// Annotation to generate TypeAdapters for the given [specs] +class GenerateAdapters { + /// Constructor + // coverage:ignore-start + const GenerateAdapters(this.specs, {this.firstTypeId = 0}); + // coverage:ignore-end + + /// The classes to generate TypeAdapters for + final List specs; + + /// The first typeId to use + final int firstTypeId; +} + +/// Configuration that specifies the generation of a TypeAdapter +class AdapterSpec { + /// Constructor + // coverage:ignore-start + const AdapterSpec(); + // coverage:ignore-end +} diff --git a/hive/pubspec.yaml b/hive/pubspec.yaml index 2e3236ef..a2de9102 100644 --- a/hive/pubspec.yaml +++ b/hive/pubspec.yaml @@ -1,6 +1,6 @@ name: hive_ce description: Hive Community Edition - A spiritual continuation of Hive v2 -version: 2.7.0+1 +version: 2.8.0 homepage: https://github.com/IO-Design-Team/hive_ce/tree/main/hive documentation: https://docs.hivedb.dev/ diff --git a/hive/readme/add_fields/person_1.dart b/hive/readme/add_fields/person_1.dart index 7a0f0e4d..90dd4798 100644 --- a/hive/readme/add_fields/person_1.dart +++ b/hive/readme/add_fields/person_1.dart @@ -1,12 +1,8 @@ import 'package:hive_ce/hive.dart'; -@HiveType(typeId: 0) class Person extends HiveObject { Person({required this.name, required this.age}); - @HiveField(0) String name; - - @HiveField(1) int age; } diff --git a/hive/readme/add_fields/person_2.dart b/hive/readme/add_fields/person_2.dart index 3257017c..59707a00 100644 --- a/hive/readme/add_fields/person_2.dart +++ b/hive/readme/add_fields/person_2.dart @@ -1,15 +1,9 @@ import 'package:hive_ce/hive.dart'; -@HiveType(typeId: 0) class Person extends HiveObject { Person({required this.name, required this.age, this.balance = 0}); - @HiveField(0) String name; - - @HiveField(1) int age; - - @HiveField(2) double balance; } diff --git a/hive/readme/store_objects/hive_adapters.dart b/hive/readme/store_objects/hive_adapters.dart new file mode 100644 index 00000000..473a5086 --- /dev/null +++ b/hive/readme/store_objects/hive_adapters.dart @@ -0,0 +1,9 @@ +import 'package:hive_ce/hive.dart'; +import 'person.dart'; + +part 'hive_adapters.g.dart'; + +@GenerateAdapters([AdapterSpec()]) +// Annotations must be on some element +// ignore: unused_element +void _() {} diff --git a/hive/readme/store_objects/hive_adapters.g.dart b/hive/readme/store_objects/hive_adapters.g.dart new file mode 100644 index 00000000..eb4cd482 --- /dev/null +++ b/hive/readme/store_objects/hive_adapters.g.dart @@ -0,0 +1 @@ +part of 'hive_adapters.dart'; diff --git a/hive/readme/store_objects/person.dart b/hive/readme/store_objects/person.dart index 7a0f0e4d..90dd4798 100644 --- a/hive/readme/store_objects/person.dart +++ b/hive/readme/store_objects/person.dart @@ -1,12 +1,8 @@ import 'package:hive_ce/hive.dart'; -@HiveType(typeId: 0) class Person extends HiveObject { Person({required this.name, required this.age}); - @HiveField(0) String name; - - @HiveField(1) int age; }