From 4d8d9db3e9a29f7b9d980020ef163e25425a347d Mon Sep 17 00:00:00 2001 From: Rexios Date: Tue, 2 Sep 2025 14:48:47 -0400 Subject: [PATCH 1/3] Add getList, getSet, getMap to boxes --- hive/lib/src/box/box.dart | 18 +++++++++++ hive/lib/src/box/box_impl.dart | 25 +++++++++++++++ hive/lib/src/box/lazy_box.dart | 18 +++++++++++ hive/lib/src/box/lazy_box_impl.dart | 31 ++++++++++++++++++ hive/lib/src/isolate/isolated_box.dart | 18 +++++++++++ .../isolated_box_impl_vm.dart | 31 ++++++++++++++++++ .../isolated_box_impl_web.dart | 31 ++++++++++++++++++ hive/lib/src/util/type_utils.dart | 32 +++++++++++++++++-- 8 files changed, 202 insertions(+), 2 deletions(-) diff --git a/hive/lib/src/box/box.dart b/hive/lib/src/box/box.dart index 6af3ee67..d18749d8 100644 --- a/hive/lib/src/box/box.dart +++ b/hive/lib/src/box/box.dart @@ -33,9 +33,27 @@ abstract class Box implements BoxBase { /// exist. E? get(dynamic key, {E? defaultValue}); + /// Read the given [key] and cast the value to [List] + List? getList(dynamic key, {List? defaultValue}); + + /// Read the given [key] and cast the value to [Set] + Set? getSet(dynamic key, {Set? defaultValue}); + + /// Read the given [key] and cast the value to [Map] + Map? getMap(dynamic key, {Map? defaultValue}); + /// Returns the value associated with the n-th key. E? getAt(int index); + /// Read the value at the given [index] and cast the value to [List] + List? getListAt(int index, {List? defaultValue}); + + /// Read the value at the given [index] and cast the value to [Set] + Set? getSetAt(int index, {Set? defaultValue}); + + /// Read the value at the given [index] and cast the value to [Map] + Map? getMapAt(int index, {Map? defaultValue}); + /// Returns a map which contains all key - value pairs of the box. Map toMap(); } diff --git a/hive/lib/src/box/box_impl.dart b/hive/lib/src/box/box_impl.dart index 20c9ce56..e28c122d 100644 --- a/hive/lib/src/box/box_impl.dart +++ b/hive/lib/src/box/box_impl.dart @@ -4,6 +4,7 @@ import 'package:hive_ce/hive.dart'; import 'package:hive_ce/src/binary/frame.dart'; import 'package:hive_ce/src/box/box_base_impl.dart'; import 'package:hive_ce/src/object/hive_object.dart'; +import 'package:hive_ce/src/util/type_utils.dart'; /// Not part of public API class BoxImpl extends BoxBaseImpl implements Box { @@ -49,6 +50,18 @@ class BoxImpl extends BoxBaseImpl implements Box { } } + @override + List? getList(dynamic key, {List? defaultValue}) => + castList(get(key), defaultValue: defaultValue); + + @override + Set? getSet(dynamic key, {Set? defaultValue}) => + castSet(get(key), defaultValue: defaultValue); + + @override + Map? getMap(dynamic key, {Map? defaultValue}) => + castMap(get(key), defaultValue: defaultValue); + @override E? getAt(int index) { checkOpen(); @@ -56,6 +69,18 @@ class BoxImpl extends BoxBaseImpl implements Box { return keystore.getAt(index)?.value as E?; } + @override + List? getListAt(int index, {List? defaultValue}) => + castList(getAt(index), defaultValue: defaultValue); + + @override + Set? getSetAt(int index, {Set? defaultValue}) => + castSet(getAt(index), defaultValue: defaultValue); + + @override + Map? getMapAt(int index, {Map? defaultValue}) => + castMap(getAt(index), defaultValue: defaultValue); + @override Future putAll(Map kvPairs) { final frames = []; diff --git a/hive/lib/src/box/lazy_box.dart b/hive/lib/src/box/lazy_box.dart index cfd135a1..1b111d6c 100644 --- a/hive/lib/src/box/lazy_box.dart +++ b/hive/lib/src/box/lazy_box.dart @@ -10,6 +10,24 @@ abstract class LazyBox extends BoxBase { /// exist. Future get(dynamic key, {E? defaultValue}); + /// Read the given [key] and cast the value to [List] + Future?> getList(dynamic key, {List? defaultValue}); + + /// Read the given [key] and cast the value to [Set] + Future?> getSet(dynamic key, {Set? defaultValue}); + + /// Read the given [key] and cast the value to [Map] + Future?> getMap(dynamic key, {Map? defaultValue}); + /// Returns the value associated with the n-th key. Future getAt(int index); + + /// Read the value at the given [index] and cast the value to [List] + Future?> getListAt(int index, {List? defaultValue}); + + /// Read the value at the given [index] and cast the value to [Set] + Future?> getSetAt(int index, {Set? defaultValue}); + + /// Read the value at the given [index] and cast the value to [Map] + Future?> getMapAt(int index, {Map? defaultValue}); } diff --git a/hive/lib/src/box/lazy_box_impl.dart b/hive/lib/src/box/lazy_box_impl.dart index 5fbf8c93..622cdef9 100644 --- a/hive/lib/src/box/lazy_box_impl.dart +++ b/hive/lib/src/box/lazy_box_impl.dart @@ -2,6 +2,7 @@ import 'package:hive_ce/hive.dart'; import 'package:hive_ce/src/binary/frame.dart'; import 'package:hive_ce/src/box/box_base_impl.dart'; import 'package:hive_ce/src/object/hive_object.dart'; +import 'package:hive_ce/src/util/type_utils.dart'; /// Not part of public API class LazyBoxImpl extends BoxBaseImpl implements LazyBox { @@ -38,11 +39,41 @@ class LazyBoxImpl extends BoxBaseImpl implements LazyBox { } } + @override + Future?> getList(dynamic key, {List? defaultValue}) async => + castList(await get(key), defaultValue: defaultValue); + + @override + Future?> getSet(dynamic key, {Set? defaultValue}) async => + castSet(await get(key), defaultValue: defaultValue); + + @override + Future?> getMap( + dynamic key, { + Map? defaultValue, + }) async => + castMap(await get(key), defaultValue: defaultValue); + @override Future getAt(int index) { return get(keystore.keyAt(index)); } + @override + Future?> getListAt(int index, {List? defaultValue}) async => + castList(await getAt(index), defaultValue: defaultValue); + + @override + Future?> getSetAt(int index, {Set? defaultValue}) async => + castSet(await getAt(index), defaultValue: defaultValue); + + @override + Future?> getMapAt( + int index, { + Map? defaultValue, + }) async => + castMap(await getAt(index), defaultValue: defaultValue); + @override Future putAll(Map kvPairs) async { checkOpen(); diff --git a/hive/lib/src/isolate/isolated_box.dart b/hive/lib/src/isolate/isolated_box.dart index 7f903b0f..22262f5e 100644 --- a/hive/lib/src/isolate/isolated_box.dart +++ b/hive/lib/src/isolate/isolated_box.dart @@ -77,8 +77,26 @@ abstract class IsolatedBoxBase { /// Get a value from the box Future get(dynamic key, {E? defaultValue}); + /// Get a value from the box and cast the value to [List] + Future?> getList(dynamic key, {List? defaultValue}); + + /// Get a value from the box and cast the value to [Set] + Future?> getSet(dynamic key, {Set? defaultValue}); + + /// Get a value from the box and cast the value to [Map] + Future?> getMap(dynamic key, {Map? defaultValue}); + /// Get a value at the given index Future getAt(int index); + + /// Get a value at the given index and cast the value to [List] + Future?> getListAt(int index, {List? defaultValue}); + + /// Get a value at the given index and cast the value to [Set] + Future?> getSetAt(int index, {Set? defaultValue}); + + /// Get a value at the given index and cast the value to [Map] + Future?> getMapAt(int index, {Map? defaultValue}); } /// Isolated version of [Box] diff --git a/hive/lib/src/isolate/isolated_box_impl/isolated_box_impl_vm.dart b/hive/lib/src/isolate/isolated_box_impl/isolated_box_impl_vm.dart index c525260b..0d4252f9 100644 --- a/hive/lib/src/isolate/isolated_box_impl/isolated_box_impl_vm.dart +++ b/hive/lib/src/isolate/isolated_box_impl/isolated_box_impl_vm.dart @@ -5,6 +5,7 @@ import 'package:hive_ce/hive.dart'; import 'package:hive_ce/src/binary/binary_reader_impl.dart'; import 'package:hive_ce/src/binary/binary_writer_impl.dart'; import 'package:hive_ce/src/isolate/isolated_hive_impl/impl/isolated_hive_impl_vm.dart'; +import 'package:hive_ce/src/util/type_utils.dart'; import 'package:isolate_channel/isolate_channel.dart'; /// Isolated implementation of [BoxBase] @@ -154,6 +155,21 @@ abstract class IsolatedBoxBaseImpl implements IsolatedBoxBase { return _readValue(result); } + @override + Future?> getList(dynamic key, {List? defaultValue}) async => + castList(await get(key), defaultValue: defaultValue); + + @override + Future?> getSet(dynamic key, {Set? defaultValue}) async => + castSet(await get(key), defaultValue: defaultValue); + + @override + Future?> getMap( + dynamic key, { + Map? defaultValue, + }) async => + castMap(await get(key), defaultValue: defaultValue); + @override Future getAt(int index) async { final bytes = @@ -161,6 +177,21 @@ abstract class IsolatedBoxBaseImpl implements IsolatedBoxBase { return _readValue(bytes); } + @override + Future?> getListAt(int index, {List? defaultValue}) async => + castList(await getAt(index), defaultValue: defaultValue); + + @override + Future?> getSetAt(int index, {Set? defaultValue}) async => + castSet(await getAt(index), defaultValue: defaultValue); + + @override + Future?> getMapAt( + int index, { + Map? defaultValue, + }) async => + castMap(await getAt(index), defaultValue: defaultValue); + Uint8List _writeValue(E value) { final writer = BinaryWriterImpl(_hive); if (_cipher != null) { diff --git a/hive/lib/src/isolate/isolated_box_impl/isolated_box_impl_web.dart b/hive/lib/src/isolate/isolated_box_impl/isolated_box_impl_web.dart index 2a11abbb..02ab370c 100644 --- a/hive/lib/src/isolate/isolated_box_impl/isolated_box_impl_web.dart +++ b/hive/lib/src/isolate/isolated_box_impl/isolated_box_impl_web.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:hive_ce/hive.dart'; +import 'package:hive_ce/src/util/type_utils.dart'; import 'package:meta/meta.dart'; /// Web implementation of [IsolatedBoxBase] @@ -93,6 +94,21 @@ abstract class IsolatedBoxBaseImpl implements IsolatedBoxBase { } } + @override + Future?> getList(dynamic key, {List? defaultValue}) async => + castList(await get(key), defaultValue: defaultValue); + + @override + Future?> getSet(dynamic key, {Set? defaultValue}) async => + castSet(await get(key), defaultValue: defaultValue); + + @override + Future?> getMap( + dynamic key, { + Map? defaultValue, + }) async => + castMap(await get(key), defaultValue: defaultValue); + @override Future getAt(int index) async { if (lazy) { @@ -102,6 +118,21 @@ abstract class IsolatedBoxBaseImpl implements IsolatedBoxBase { } } + @override + Future?> getListAt(int index, {List? defaultValue}) async => + castList(await getAt(index), defaultValue: defaultValue); + + @override + Future?> getSetAt(int index, {Set? defaultValue}) async => + castSet(await getAt(index), defaultValue: defaultValue); + + @override + Future?> getMapAt( + int index, { + Map? defaultValue, + }) async => + castMap(await getAt(index), defaultValue: defaultValue); + @override bool operator ==(Object other) { return other is IsolatedBoxBaseImpl && other._box == _box; diff --git a/hive/lib/src/util/type_utils.dart b/hive/lib/src/util/type_utils.dart index eae2e2c0..ecdcacab 100644 --- a/hive/lib/src/util/type_utils.dart +++ b/hive/lib/src/util/type_utils.dart @@ -15,12 +15,40 @@ void typedMapOrIterableCheck() { final type = E.toString(); if (type.startsWith('Map<') && type != 'Map') { throw AssertionError( - 'Cannot open a box of type $type. It is not possible to read typed Maps. Use Map.cast() after reading.', + 'Cannot open a box of type $type. Instead open a `Box` and call `getMap()`.', ); } else if ({'Iterable<', 'List<', 'Set<'}.any(type.startsWith) && !_allowedIterableTypes.any(type.endsWith)) { + final iterableType = type.substring(0, type.indexOf('<')); throw AssertionError( - 'Cannot open a box of type $type. It is not possible to read Iterables of custom types. Use Iterable.cast() after reading.', + 'Cannot open a box of type $type. Instead open a `Box<$iterableType>` and call `get$iterableType()`.', ); } } + +/// Common logic for casting a typed list +List? castList(Object? value, {List? defaultValue}) { + if (value != null) { + return (value as List).cast(); + } else { + return defaultValue; + } +} + +/// Common logic for casting a typed set +Set? castSet(Object? value, {Set? defaultValue}) { + if (value != null) { + return (value as Set).cast(); + } else { + return defaultValue; + } +} + +/// Common logic for casting a typed map +Map? castMap(Object? value, {Map? defaultValue}) { + if (value != null) { + return (value as Map).cast(); + } else { + return defaultValue; + } +} From e4043e99621a86dca6bbee7f730ad44a1cfc6596 Mon Sep 17 00:00:00 2001 From: Rexios Date: Tue, 2 Sep 2025 14:50:51 -0400 Subject: [PATCH 2/3] Testing --- hive/test/tests/box/box_impl_test.dart | 56 ++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/hive/test/tests/box/box_impl_test.dart b/hive/test/tests/box/box_impl_test.dart index 92e2f7f0..71648edb 100644 --- a/hive/test/tests/box/box_impl_test.dart +++ b/hive/test/tests/box/box_impl_test.dart @@ -94,6 +94,62 @@ void main() { expect(box.getAt(1), 'A'); }); + group('Iterable and Map casting', () { + test('.getList()', () { + final keystore = Keystore.debug(frames: [ + Frame(0, [1, 2, 3]), + ]); + final box = _getBox(keystore: keystore); + + expect(box.getList(0), [1, 2, 3]); + }); + + test('.getSet()', () { + final keystore = Keystore.debug(frames: [ + Frame(0, {1, 2, 3}), + ]); + final box = _getBox(keystore: keystore); + + expect(box.getSet(0), {1, 2, 3}); + }); + + test('.getMap()', () { + final keystore = Keystore.debug(frames: [ + Frame(0, {'a': 1, 'b': 2, 'c': 3}), + ]); + final box = _getBox(keystore: keystore); + + expect(box.getMap(0), {'a': 1, 'b': 2, 'c': 3}); + }); + + test('.getListAt()', () { + final keystore = Keystore.debug(frames: [ + Frame(0, [1, 2, 3]), + ]); + final box = _getBox(keystore: keystore); + + expect(box.getListAt(0), [1, 2, 3]); + }); + + test('.getSetAt()', () { + final keystore = Keystore.debug(frames: [ + Frame(0, {1, 2, 3}), + ]); + final box = _getBox(keystore: keystore); + + expect(box.getSetAt(0), {1, 2, 3}); + }); + + test('.getMapAt()', () { + final keystore = Keystore.debug(frames: [ + Frame(0, {'a': 1, 'b': 2, 'c': 3}), + ]); + final box = _getBox(keystore: keystore); + + expect(box.getMapAt(0), {'a': 1, 'b': 2, 'c': 3}); + }); + }); + group('.putAll()', () { test('values', () async { final frames = [ From d1a9cf5c743e647907109e4b7e90d3cbf119369d Mon Sep 17 00:00:00 2001 From: Rexios Date: Tue, 2 Sep 2025 15:05:44 -0400 Subject: [PATCH 3/3] Testing --- hive/test/tests/box/lazy_box_impl_test.dart | 78 ++++++++++++++++--- .../test/tests/isolate/isolated_box_test.dart | 44 +++++++++++ .../tests/isolate/isolated_lazy_box_test.dart | 44 +++++++++++ 3 files changed, 156 insertions(+), 10 deletions(-) diff --git a/hive/test/tests/box/lazy_box_impl_test.dart b/hive/test/tests/box/lazy_box_impl_test.dart index 3f1b2533..82368d08 100644 --- a/hive/test/tests/box/lazy_box_impl_test.dart +++ b/hive/test/tests/box/lazy_box_impl_test.dart @@ -18,12 +18,21 @@ LazyBoxImpl _getBox({ CompactionStrategy? cStrategy, StorageBackend? backend, }) { + if (backend == null) { + backend = MockStorageBackend(); + + for (final frame in keystore?.frames ?? []) { + when(() => backend!.readValue(frame)) + .thenAnswer((i) async => frame.value); + } + } + final box = LazyBoxImpl( hive ?? HiveImpl(), name ?? 'testBox', null, cStrategy ?? (total, deleted) => false, - backend ?? MockStorageBackend(), + backend, ); box.keystore = keystore ?? Keystore(box, ChangeNotifier(), null); return box; @@ -60,20 +69,69 @@ void main() { test('.getAt()', () async { final keystore = Keystore.debug( - frames: [ - Frame.lazy(0), - Frame.lazy('a'), - ], + frames: [Frame(0, 'A'), Frame('a', 'A')], ); - final backend = MockStorageBackend(); - when(() => backend.readValue(any())).thenAnswer((i) { - return Future.value('A'); - }); - final box = _getBox(keystore: keystore, backend: backend); + final box = _getBox(keystore: keystore); expect(await box.getAt(1), 'A'); }); + group('Iterable and Map casting', () { + test('.getList()', () async { + final keystore = Keystore.debug(frames: [ + Frame(0, [1, 2, 3]), + ]); + final box = _getBox(keystore: keystore); + + expect(await box.getList(0), [1, 2, 3]); + }); + + test('.getSet()', () async { + final keystore = Keystore.debug(frames: [ + Frame(0, {1, 2, 3}), + ]); + final box = _getBox(keystore: keystore); + + expect(await box.getSet(0), {1, 2, 3}); + }); + + test('.getMap()', () async { + final keystore = Keystore.debug(frames: [ + Frame(0, {'a': 1, 'b': 2, 'c': 3}), + ]); + final box = _getBox(keystore: keystore); + + expect(await box.getMap(0), {'a': 1, 'b': 2, 'c': 3}); + }); + + test('.getListAt()', () async { + final keystore = Keystore.debug(frames: [ + Frame(0, [1, 2, 3]), + ]); + final box = _getBox(keystore: keystore); + + expect(await box.getListAt(0), [1, 2, 3]); + }); + + test('.getSetAt()', () async { + final keystore = Keystore.debug(frames: [ + Frame(0, {1, 2, 3}), + ]); + final box = _getBox(keystore: keystore); + + expect(await box.getSetAt(0), {1, 2, 3}); + }); + + test('.getMapAt()', () async { + final keystore = Keystore.debug(frames: [ + Frame(0, {'a': 1, 'b': 2, 'c': 3}), + ]); + final box = _getBox(keystore: keystore); + + expect(await box.getMapAt(0), {'a': 1, 'b': 2, 'c': 3}); + }); + }); + group('.putAll()', () { test('values', () async { final backend = MockStorageBackend(); diff --git a/hive/test/tests/isolate/isolated_box_test.dart b/hive/test/tests/isolate/isolated_box_test.dart index 2d57df2c..333bc701 100644 --- a/hive/test/tests/isolate/isolated_box_test.dart +++ b/hive/test/tests/isolate/isolated_box_test.dart @@ -84,6 +84,50 @@ void main() { expect(await box.getAt(1), 'A'); }); + group('Iterable and Map casting', () { + test('.getList()', () async { + final box = await _openBox(frames: [ + Frame(0, [1, 2, 3]) + ]); + expect(await box.getList(0), [1, 2, 3]); + }); + + test('.getSet()', () async { + final box = await _openBox(frames: [ + Frame(0, {1, 2, 3}) + ]); + expect(await box.getSet(0), {1, 2, 3}); + }); + + test('.getMap()', () async { + final box = await _openBox(frames: [ + Frame(0, {'a': 1, 'b': 2, 'c': 3}) + ]); + expect(await box.getMap(0), {'a': 1, 'b': 2, 'c': 3}); + }); + + test('.getListAt()', () async { + final box = await _openBox(frames: [ + Frame(0, [1, 2, 3]) + ]); + expect(await box.getListAt(0), [1, 2, 3]); + }); + + test('.getSetAt()', () async { + final box = await _openBox(frames: [ + Frame(0, {1, 2, 3}) + ]); + expect(await box.getSetAt(0), {1, 2, 3}); + }); + + test('.getMapAt()', () async { + final box = await _openBox(frames: [ + Frame(0, {'a': 1, 'b': 2, 'c': 3}) + ]); + expect(await box.getMapAt(0), {'a': 1, 'b': 2, 'c': 3}); + }); + }); + group('.putAll()', () { test('values', () async { final box = await _openBox(); diff --git a/hive/test/tests/isolate/isolated_lazy_box_test.dart b/hive/test/tests/isolate/isolated_lazy_box_test.dart index de778ed1..6d4f5f04 100644 --- a/hive/test/tests/isolate/isolated_lazy_box_test.dart +++ b/hive/test/tests/isolate/isolated_lazy_box_test.dart @@ -56,6 +56,50 @@ void main() { expect(await box.getAt(1), 'A'); }); + group('Iterable and Map casting', () { + test('.getList()', () async { + final box = await _openBox(frames: [ + Frame(0, [1, 2, 3]) + ]); + expect(await box.getList(0), [1, 2, 3]); + }); + + test('.getSet()', () async { + final box = await _openBox(frames: [ + Frame(0, {1, 2, 3}) + ]); + expect(await box.getSet(0), {1, 2, 3}); + }); + + test('.getMap()', () async { + final box = await _openBox(frames: [ + Frame(0, {'a': 1, 'b': 2, 'c': 3}) + ]); + expect(await box.getMap(0), {'a': 1, 'b': 2, 'c': 3}); + }); + + test('.getListAt()', () async { + final box = await _openBox(frames: [ + Frame(0, [1, 2, 3]) + ]); + expect(await box.getListAt(0), [1, 2, 3]); + }); + + test('.getSetAt()', () async { + final box = await _openBox(frames: [ + Frame(0, {1, 2, 3}) + ]); + expect(await box.getSetAt(0), {1, 2, 3}); + }); + + test('.getMapAt()', () async { + final box = await _openBox(frames: [ + Frame(0, {'a': 1, 'b': 2, 'c': 3}) + ]); + expect(await box.getMapAt(0), {'a': 1, 'b': 2, 'c': 3}); + }); + }); + group('.putAll()', () { test('values', () async { final box = await _openBox();