Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Hive working with @immutable class #225

Closed
peekpt opened this issue Feb 14, 2020 · 33 comments
Closed

Make Hive working with @immutable class #225

peekpt opened this issue Feb 14, 2020 · 33 comments
Labels
enhancement New feature or request

Comments

@peekpt
Copy link

peekpt commented Feb 14, 2020

@leisim Hi there!
It will be awesome to get this awesome plugin Freezed working with immutable models, so we can have immutable model that can be generated and stored directly in Hive database.

cc: @rrousselGit

@peekpt peekpt added the enhancement New feature or request label Feb 14, 2020
@simc
Copy link
Member

simc commented Feb 14, 2020

Thanks, I'll take a look. It will be hard to achieve this with the changes planned for Hive 2.0 but I'll try to figure it out.

@frank06
Copy link

frank06 commented Feb 17, 2020

That would be great! I remember some time ago trying to tag my models as @immutable but got warnings as HiveObjects are mutable

@peekpt
Copy link
Author

peekpt commented Feb 17, 2020

@frank06 exactly that!

@rrousselGit
Copy link

I suppose that instead of "working with Freezed", it's more of a "working with immutable models".

Freezed doesn't do anything fancy.

@peekpt peekpt changed the title Make Hive working with Freezed Make Hive working with @immutable class Feb 17, 2020
@MarcelGarus
Copy link
Contributor

MarcelGarus commented Apr 5, 2020

I'd also really love this to work. I'll try to find some time to look into the issues that occur when using hive with freezed. It would make our data classes so much shorter!

EDIT: I just started looking into it, but because the hive_generator repo got archived, I decided it's not worth the effort.

Hand-written immutable Hive data classes
@HiveType(typeId: TypeId.user)
class User implements Entity<User> {
  const User({
    @required this.id,
    @required this.firstName,
    @required this.lastName,
    @required this.email,
    @required this.schoolId,
    String displayName,
    @required this.avatarInitials,
    @required this.avatarBackgroundColor,
    @required this.permissions,
    @required this.roleIds,
  })  : assert(id != null),
        assert(firstName != null),
        assert(lastName != null),
        assert(email != null),
        assert(schoolId != null),
        displayName = displayName ?? '$firstName $lastName',
        assert(avatarInitials != null),
        assert(avatarBackgroundColor != null),
        assert(permissions != null),
        assert(roleIds != null);

  User.fromJson(Map<String, dynamic> data)
      : this(
          id: Id<User>(data['_id']),
          firstName: data['firstName'],
          lastName: data['lastName'],
          email: data['email'],
          schoolId: data['schoolId'],
          displayName: data['displayName'],
          avatarInitials: data['avatarInitials'],
          avatarBackgroundColor:
              (data['avatarBackgroundColor'] as String).hexToColor,
          permissions: (data['permissions'] as List<dynamic>).cast<String>(),
          roleIds: parseIds(data['roles']),
        );

  static Future<User> fetch(Id<User> id) async =>
      User.fromJson(await services.api.get('users/$id').json);

  @override
  @HiveField(0)
  final Id<User> id;

  @HiveField(1)
  final String firstName;

  @HiveField(2)
  final String lastName;

  String get shortName => '${firstName.chars.first}. $lastName';

  @HiveField(3)
  final String email;

  @HiveField(4)
  final String schoolId;

  @HiveField(5)
  final String displayName;

  @HiveField(7)
  final String avatarInitials;
  @HiveField(8)
  final Color avatarBackgroundColor;

  @HiveField(6)
  final List<String> permissions;
  bool hasPermission(String permission) => permissions.contains(permission);

  @HiveField(9)
  final List<Id<Role>> roleIds;
  bool get isTeacher => hasRole(Role.teacherName);
  bool hasRole(String name) {
    // TODO(marcelgarus): Remove the hard-coded mapping and use runtime lookup when upgrading flutter_cached and flattening is supported.
    final id = {
      Role.teacherName: '0000d186816abba584714c98',
    }[name];
    return id != null && roleIds.contains(Id<Role>(id));
  }

  @override
  bool operator ==(Object other) =>
      other is User &&
      id == other.id &&
      firstName == other.firstName &&
      lastName == other.lastName &&
      email == other.email &&
      schoolId == other.schoolId &&
      displayName == other.displayName &&
      avatarInitials == other.avatarInitials &&
      avatarBackgroundColor == other.avatarBackgroundColor &&
      permissions.deeplyEquals(other.permissions, unordered: true) &&
      roleIds.deeplyEquals(other.roleIds, unordered: true);
  @override
  int get hashCode => hashList([
        id,
        firstName,
        lastName,
        email,
        schoolId,
        displayName,
        avatarInitials,
        avatarBackgroundColor,
        permissions,
        roleIds
      ]);
}
Using freezed with Hive (doesn't work yet)
@freezed
@HiveType(typeId: TypeId.user)
abstract class User implements Entity<User>, _$User {
  const User._();
  const factory User({
    @HiveField(0) @required Id<User> id,
    @HiveField(1) @required String firstName,
    @HiveField(2) @required String lastName,
    @HiveField(3) @required String email,
    @HiveField(4) @required String schoolId,
    @HiveField(5) String displayName,
    @HiveField(7) @required String avatarInitials,
    @HiveField(8) @required Color avatarBackgroundColor,
    @HiveField(6) @required List<String> permissions,
    @HiveField(9) @required List<Id<Role>> roleIds,
  }) = _User;
  // displayName = displayName ?? '$firstName $lastName',

  static User fromJson(Map<String, dynamic> data) => User(
        id: Id<User>(data['_id']),
        firstName: data['firstName'],
        lastName: data['lastName'],
        email: data['email'],
        schoolId: data['schoolId'],
        displayName: data['displayName'],
        avatarInitials: data['avatarInitials'],
        avatarBackgroundColor:
            (data['avatarBackgroundColor'] as String).hexToColor,
        permissions: (data['permissions'] as List<dynamic>).cast<String>(),
        roleIds: parseIds(data['roles']),
      );

  static Future<User> fetch(Id<User> id) async =>
      User.fromJson(await services.api.get('users/$id').json);

  String get shortName => '${firstName.chars.first}. $lastName';

  bool hasPermission(String permission) => permissions.contains(permission);

  bool get isTeacher => hasRole(Role.teacherName);
  bool hasRole(String name) {
    // TODO(marcelgarus): Remove the hard-coded mapping and use runtime lookup when upgrading flutter_cached and flattening is supported.
    final id = {
      Role.teacherName: '0000d186816abba584714c98',
    }[name];
    return id != null; // && roleIds.contains(Id<Role>(id));
  }
}

@rubgithub
Copy link

I'd also really love this to work. I'll try to find some time to look into the issues that occur when using hive with freezed. It would make our data classes so much shorter!

EDIT: I just started looking into it, but because the hive_generator repo got archived, I decided it's not worth the effort.

Hand-written immutable Hive data classes
Using freezed with Hive (doesn't work yet)

Any update about this feature?

@MarcelGarus
Copy link
Contributor

Sure. I have a kind of love-hate relationship with the Hive serializer. I think it got a lot of things right, but I also strongly disagree with some of the design decisions (like the format not being self-descriptive, like JSON, or the adapters having the responsibility to define their own ids, causing IDs to be scattered all over your project instead of being defined in one central place). I also wanted to use the serializer on its own separate from Hive, so in #152 I did some brainstorming with @leisim about how the serializer could be improved – and several great ideas came up. I wanted to play around with some of them, so I started writing a serializer with a slightly different approach, which ended up in tape. I also focused on great tooling, but it's not production-ready by a long shot. For the Hive replacement – isar@leisim wrote a completely new serializer with some different design philosophies which also looks promising.

Tape does work with freezed, and I think the part of being able to use freezed could also be applied to Hive – I'm currently quite busy with studying and work stuff as well as other projects, so it's probably gonna take some time before I find time to look at it though. If anyone has spare time and feels like it, don't hesitate to code something up and file a PR. Tape already works with freezed, so in the repo you should find some guidance on how that could work. I'm also available for concrete questions, if you have any.

@zs-dima
Copy link

zs-dima commented Sep 21, 2020

Any updates?

@themisir
Copy link
Contributor

What do you mean by "working with @immutable class"? I haven't used freezed package, so I don't know what's exact issue.

@zs-dima
Copy link

zs-dima commented Sep 21, 2020

"@immutable class" is not about freezed - you could read thread history for more details
freezed just example
it is just Hive blocker issue that force many users to use Sembast instead

@themisir
Copy link
Contributor

themisir commented Sep 21, 2020

"@immutable class" is not about freezed - you could read thread history for more details
freezed just example
it is just Hive blocker issue that force many users to use Sembast instead

I have tried adding @immutable to my hive models. But did not get any warnings or errors.

Does the issue is related to model adapters generated by hive_generator or caused when extending models with HiveObject?

image

@zs-dima
Copy link

zs-dima commented Sep 21, 2020

@immutable to my hive models
"@immutable classes" usually constructed using with or implements
so it will compile and run but it will store empty objects in the Hive DB.

I like Hive so hope it will be fixed once.

for example with freezed immutable class looks like:

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive/hive.dart';

part 'immutable_class.freezed.dart';
part 'immutable_class.g.dart';

@freezed
abstract class ImmutableClass with _$ImmutableClass {
  @HiveType(typeId: 5)
  const factory ImmutableClass({
    @JsonKey(name: 'id', required: true, disallowNullValue: true) @HiveField(0) int id,
    @HiveField(1) int someField1,
    @HiveField(2) String someField2,
  }) = _ImmutableClass;

  factory ImmutableClass.fromJson(Map<String, dynamic> json) => _$ImmutableClassFromJson(json);
}

@themisir
Copy link
Contributor

themisir commented Sep 21, 2020

I'm not planning to implement this feature. The implementation will be package specific (freezed in your example). Instead you can write your own hive adapter. Or at least human readable model classes (which hive generator currently supports). Fyi: hive_generator generates code for fields annotated with HiveField inside classes mentioned with HiveType. It doesn't checks for constructor arguments or something else.

@zs-dima
Copy link

zs-dima commented Sep 21, 2020

Dart have no @immutable classes other then packages specific built_value freezed and etc (@immutable attribute only will not class immutable)
So user have to choose reliable code with specific packages or non-reliable code with Hive - therefore many forced to use Sembast instead of Hive

@zs-dima
Copy link

zs-dima commented Sep 21, 2020

moreover

hive_generator generates code for fields annotated with HiveField inside classes mentioned with HiveType.

-- freezed generate fields annotated with HiveField inside classes mentioned with HiveType (as base class or mixin)
-- and Hive still do not save objects correctly

@zs-dima
Copy link

zs-dima commented Sep 21, 2020

the only way I found to workarround it some-how, but it looks quite ugly with adapter name string, hope Hive will be improved to fix it:

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive/hive.dart';

part 'immutable_class.freezed.dart';
part 'immutable_class.g.dart';

@freezed
abstract class ImmutableClass with _$ImmutableClass {
  @HiveType(typeId: 5, adapterName: 'ImmutableClassAdapter')
  const factory ImmutableClass({
    @JsonKey(name: 'id', required: true, disallowNullValue: true) @HiveField(0) int id,
    @HiveField(1) int someField1,
    @HiveField(2) String someField2,
  }) = _ImmutableClass;

  factory ImmutableClass.fromJson(Map<String, dynamic> json) => _$ImmutableClassFromJson(json);
}

@themisir
Copy link
Contributor

themisir commented Sep 21, 2020

What do you think about that, @leisim ?

@RaviKavaiya
Copy link

the only way I found to workarround it some-how, but it looks quite ugly with adapter name string, hope Hive will be improved to fix it:

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive/hive.dart';

part 'immutable_class.freezed.dart';
part 'immutable_class.g.dart';

@freezed
abstract class ImmutableClass with _$ImmutableClass {
  @HiveType(typeId: 5, adapterName: 'ImmutableClassAdapter')
  const factory ImmutableClass({
    @JsonKey(name: 'id', required: true, disallowNullValue: true) @HiveField(0) int id,
    @HiveField(1) int someField1,
    @HiveField(2) String someField2,
  }) = _ImmutableClass;

  factory ImmutableClass.fromJson(Map<String, dynamic> json) => _$ImmutableClassFromJson(json);
}

@themisir I have been using this solution since last 3-4 months. And I didn't get any issues.
The problem with using Hive with Freezed is, Freezed creates Private classes (_$*), so for example, our class Product will have adapter called _$ProductAdapter, which we won't be able to access anywhere.

The solution is to manually provide adapterName just as this solution and it would work PERFECTLY.

So, to conclude, neither of the libraries are at fault.

@zs-dima
Copy link

zs-dima commented Sep 22, 2020

It's ugly to use String as name
What about trim start '_' and '$' symbols from adapter name by default? As adapter should not be private

@themisir
Copy link
Contributor

So the problem is adapter names. I can trim _$ part from adapter names.

@zs-dima
Copy link

zs-dima commented Sep 22, 2020

I can trim _$ part from adapter names.

It could be awesome, thanks a lot

@themisir
Copy link
Contributor

themisir commented Sep 23, 2020

Published to pub.dev. You will need to set min version to 0.7.2+1:

dev_dependencies:
  hive_generator: ^0.7.2+1

@zs-dima
Copy link

zs-dima commented Sep 23, 2020

@themisir

Adapter still have '_' sign:

class _SomeClassAdapter extends TypeAdapter<_$_SomeClass> {

Thanks for the fast update, hope you will fix it as adapter name started with '_' sign could not be really used

Thanks in advance

@themisir
Copy link
Contributor

@themisir

Adapter still have '_' sign:

class _SomeClassAdapter extends TypeAdapter<_$_SomeClass> {

Thanks for the fast update, hope you will fix it as adapter name started with '_' sign could not be really used

Thanks in advance

Oops 😅 Now fixing.

@themisir
Copy link
Contributor

Fixed and published!

@zs-dima
Copy link

zs-dima commented Sep 23, 2020

Wow, Adapters are generated correctly, awesome, thanks a lot for the fast fix!

@Mravuri96
Copy link

any update to the docs, regarding these changes?

@mohammadne
Copy link

mohammadne commented Oct 20, 2020

is it possible to have sth like this for union classes ?

HIVE

@HiveType(typeId: 2)
enum AudioOrderDao {
  @HiveField(0)
  order,

  @HiveField(2)
  repeatAll,

  @HiveField(1)
  repeatOne,

  @HiveField(3)
  shuffle
}

FREEZED

@freezed
abstract class AudioOrder with _$AudioOrder {
  const factory AudioOrder.order() = _Order;
  const factory AudioOrder.repeatAll() = _RepeatAll;
  const factory AudioOrder.repeatOne() = _RepeatOne;
  const factory AudioOrder.shuffle() = _Shuffle;
}

thanks

@themisir
Copy link
Contributor

themisir commented Oct 20, 2020

is it possible to have sth like this for union classes ?

HIVE

@HiveType(typeId: 2)
enum AudioOrderDao {
  @HiveField(0)
  order,

  @HiveField(2)
  repeatAll,

  @HiveField(1)
  repeatOne,

  @HiveField(3)
  shuffle
}

FREEZED

@freezed
abstract class AudioOrder with _$AudioOrder {
  const factory AudioOrder.order() = _Order;
  const factory AudioOrder.repeatAll() = _RepeatAll;
  const factory AudioOrder.repeatOne() = _RepeatOne;
  const factory AudioOrder.shuffle() = _Shuffle;
}

thanks

The first is enum and second one is class. They are not same thing. Anyways you can create custom type adapter for that.

@bibekmakaju
Copy link

the only way I found to workarround it some-how, but it looks quite ugly with adapter name string, hope Hive will be improved to fix it:

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive/hive.dart';

part 'immutable_class.freezed.dart';
part 'immutable_class.g.dart';

@freezed
abstract class ImmutableClass with _$ImmutableClass {
  @HiveType(typeId: 5, adapterName: 'ImmutableClassAdapter')
  const factory ImmutableClass({
    @JsonKey(name: 'id', required: true, disallowNullValue: true) @HiveField(0) int id,
    @HiveField(1) int someField1,
    @HiveField(2) String someField2,
  }) = _ImmutableClass;

  factory ImmutableClass.fromJson(Map<String, dynamic> json) => _$ImmutableClassFromJson(json);
}

@themisir I have been using this solution since last 3-4 months. And I didn't get any issues. The problem with using Hive with Freezed is, Freezed creates Private classes (_$*), so for example, our class Product will have adapter called _$ProductAdapter, which we won't be able to access anywhere.

The solution is to manually provide adapterName just as this solution and it would work PERFECTLY.

So, to conclude, neither of the libraries are at fault.

how to register this adapter?

@Shiba-Kar
Copy link

@bibekmakaju

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Hive.initFlutter();
  Hive.registerAdapter<Employee>(EmployeeType());
  Hive.registerAdapter<Role>(RoleType());
  await Hive.openBox<List<Employee>>('employees_box');
  runApp(const ProviderScope(child: MyApp()));
}

@rootux
Copy link

rootux commented Oct 3, 2023

Can't find my adapter and therefore can't register it
Tried to use
Hive.registerAdapter(ProfileAdapter()); - but I don't have access to Profiles adatper.

Here is some of the code

part 'profile.freezed.dart';
part 'profile.g.dart';

@freezed
class Profile with _$Profile{
  @HiveType(typeId: 5, adapterName: "ProfileAdapter")
  const factory Profile({
    @HiveField(1)
    required String name,
    @HiveField(2)
    required List<String> ideas,
    @HiveField(3)
    @Default(false) bool isAllowSomething
  }) = _Profile;

The generated profile.g.dart file does have

class ProfileAdapter extends TypeAdapter<_$ProfileImpl> {

But I can't seem to be able to access it to register it as an adapter

@rootux
Copy link

rootux commented Oct 3, 2023

UPDATE
❌ Small progress - it writes to hive - but when decoding (reading) from hive -
On the latest freezed version I get
"type '_$ProfileImpl' is not a subtype of type 'Map<String, dynamic>' in type cast"

And for an older freezed of 2.3.5 I get
type '_$_Profile' is not a subtype of type 'Map<String, dynamic>' in type cast

For some reason the code snippets above didn't work for me - than I've set the @HiveType on the class and not on the factory like so :

part 'profile.freezed.dart';
part 'profile.g.dart';

@freezed
@HiveType(typeId: 5, adapterName: "ProfileAdapter")
class Profile with _$Profile{
  const factory Profile({
    @HiveField(1)
    required String name,
    @HiveField(2)
    required List<String> ideas,
    @HiveField(3)
    @Default(false) bool isAllowSomething
  }) = _Profile;

This is what being created

class ProfileAdapter extends TypeAdapter<Profile> {

which can be used when registering the adapter.

the issue is probably around those lines:

class ProfileAdapter extends TypeAdapter<_$_Profile> {
  @override
  final int typeId = 5;

  @override
  _$_Profile read(BinaryReader reader) {

the read method returns a _$_Profile where it might needs to read back a Profile
And does write but doesn't read back (See error in top message)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests