When get List<T> Hive V4 throws 'Type Mismatch' error
I have tried to make module from Hive with Generic models. Created 'CacheModel' mixin like this:
mixin CacheModel {
String get id;
CacheModel fromDynamicJson(dynamic json);
Map<String, dynamic> toJson();
}
And a manager to registerAdapters like this:
final class HiveCacheManager extends CacheManager {
HiveCacheManager({super.path});
@override
Future<void> init({required List<CacheModel> cacheModels}) async {
final documentPath =
path ?? (await getApplicationDocumentsDirectory()).path;
Hive.defaultDirectory = documentPath;
for (final cacheModel in cacheModels) {
Hive.registerAdapter(
'${cacheModel.runtimeType}', cacheModel.fromDynamicJson);
}
}
@override
void remove() {
Hive.deleteAllBoxesFromDisk();
}
}
CacheManager is an abstract class and have a CacheOperation abstract class like this:
abstract class CacheOperation<T extends CacheModel> {
void add(T item);
void addAll(List<T> items);
void remove(String id);
void clear();
List<T> getAll();
T? get(String id);
T? getFirst();
}
I also have a HiveCacheOperation which extends CacheOperation of T Type like this:
final class HiveCacheOperation<T extends CacheModel> extends CacheOperation<T> {
HiveCacheOperation() {
_box = Hive.box<T>(name: T.toString());
}
late final Box<T> _box;
@override
void add(T item) {
_box.put(item.id, item);
}
@override
void addAll(List<T> items) {
_box.putAll(Map.fromIterable(
items,
key: (element) => (element as T).id,
value: (element) => (element as T),
));
}
@override
void clear() {
_box.clear();
}
@override
T? get(String id) {
return _box.get(id);
}
@override
T? getFirst() {
return _box.get(_box.keys.first);
}
bool countBoxKeys() {
if (_box.isOpen) {
final count = _box.getAll(_box.keys).cast<T>().isNotEmpty;
return count;
}
return false;
}
@override
List<T> getAll() {
return _box
.getAll(_box.keys)
.where((element) => element != null)
.cast<T>()
.toList();
}
@override
void remove(String id) {
_box.delete(id);
}
}
I created a UserCacheModel and extend it from CacheModel like this:
final class UserCacheModel with CacheModel {
UserCacheModel({
required this.user,
});
UserCacheModel.empty() : user = const UserModel();
final UserModel user;
@override
CacheModel fromDynamicJson(json) {
final mapCast = json as Map<String, dynamic>?;
if (mapCast == null) return this;
return copyWith(userModel: UserModel.fromJson(mapCast));
}
@override
String get id => user.id.toString();
@override
Map<String, dynamic> toJson() {
return user.toJson();
}
UserCacheModel copyWith({UserModel? userModel}) {
return UserCacheModel(user: userModel ?? user);
}
}
Finally created a ProductCache which required CacheManager like this:
final class ProductCache {
ProductCache({required CacheManager cacheManager})
: _cacheManager = cacheManager;
final CacheManager _cacheManager;
Future<void> init() async {
await _cacheManager.init(
cacheModels: [
CorporationCacheModel(corporation: const CorporationModel()),
UserCacheModel(user: const UserModel()),
],
);
}
late final HiveCacheOperation<UserCacheModel> userCacheOperation =
HiveCacheOperation<UserCacheModel>();
late final HiveCacheOperation<CorporationCacheModel>
corporationCacheOperation = HiveCacheOperation<CorporationCacheModel>();
}
But when i try to call .getAll function of userCacheOperation i got "Invalid argument(s): Type mismatch. Expected UserCacheModel but got CorporationCacheModel.".
So is this a registerAdapter error or something else?
Resolving Type Mismatch Error in Hive with Generic Models
The error "Invalid argument(s): Type mismatch. Expected UserCacheModel but got CorporationCacheModel." typically occurs when there's a mix-up with how adapters are registered or how Hive boxes are opened with specific types. This issue is often related to improper handling of type registrations or incorrect usage of Hive's box and adapter mechanisms, especially when dealing with generic models and mixins.
Root Cause of the Issue
The problem arises because the adapters for different types (UserCacheModel, CorporationCacheModel, etc.) might not be correctly registered, leading Hive to confuse one type for another. Specifically, Hive needs each type to be uniquely identified and registered with its corresponding adapter function.
Solution Implemented in the PR
In the pull request #1310 , the issue has been resolved by ensuring that each CacheModel type is registered with Hive uniquely and appropriately.
Key Changes in the PR:
-
Added Type Parameter in Adapter Registration:
- A
Typeparameter was added to ensure that each adapter is registered uniquely based on its runtime type. This prevents type mismatches by explicitly linking the adapter with the correct model type.
- A
-
Correct Use of
registerAdapterMethod:- The
registerAdaptermethod was modified to include type safety checks and ensure that each type's adapter is only registered once.
- The
-
Enhanced Adapter Registration Logic:
- The registration process was updated to use a specific identifier (like the runtime type) to differentiate between various models when registering adapters.
How to Use the Solution
To use the fixed version of Hive with your generic models, follow these steps:
-
Update Your Dependency:
- Use the specific branch or commit from the PR to pull the updated version of Hive that contains the fix:
dependencies: flutter: sdk: flutter hive: git: url: https://github.com/isar/hive.git ref: 5eb6fe59fdb70d332a27102ecccd4efc0bfde7fd -
Initialize the Cache Manager Correctly:
Make sure your HiveCacheManager is set up as follows to initialize and register the adapters properly:
final class HiveCacheManager extends CacheManager {
HiveCacheManager({super.path});
@override
Future<void> init({required List<CacheModel> cacheModels}) async {
final documentPath = path ?? (await getApplicationDocumentsDirectory()).path;
Hive.defaultDirectory = documentPath;
for (final cacheModel in cacheModels) {
Hive.registerAdapter(
'${cacheModel.runtimeType}',
cacheModel.fromDynamicJson,
cacheModel.runtimeType, // Ensure the correct type is registered
);
}
}
@override
void remove() {
Hive.deleteAllBoxesFromDisk();
}
}
- Verify Usage in Your ProductCache:
Ensure that each operation on your cache models, like getAll, correctly references the unique adapters registered for those models:
final class ProductCache {
ProductCache({required CacheManager cacheManager})
: _cacheManager = cacheManager;
final CacheManager _cacheManager;
Future<void> init() async {
await _cacheManager.init(
cacheModels: [
CorporationCacheModel(corporation: const CorporationModel()),
UserCacheModel(user: const UserModel()),
],
);
}
late final HiveCacheOperation<UserCacheModel> userCacheOperation =
HiveCacheOperation<UserCacheModel>();
late final HiveCacheOperation<CorporationCacheModel> corporationCacheOperation =
HiveCacheOperation<CorporationCacheModel>();
}
[!NOTE]
By following these steps and using the changes implemented in the PR, you should be able to resolve the type mismatch error and use the getAll function correctly with your generic models in Hive. If you encounter further issues, ensure that each model's adapter is correctly registered and that no adapters are overridden during initialization.
[!WARNING]
For the solution to work correctly, ensure to clean the Hive cache once initially using Hive.deleteAllBoxesFromDisk() before registering the adapters. This step helps to avoid type conflicts from previously cached data.
[!TIP]
And hey, if you'd like to buy me a coffee, your code might just run even better! 😉