@@ -121,6 +121,9 @@ abstract class SerializableStorageContainer<E extends Object> extends StorageCon
121121 /// behavior in testing scenarios.
122122 final Random _random;
123123
124+ /// Internal cache of item holders by their keys.
125+ final Map <String , SerializableItemHolder <E >> _holders = {};
126+
124127 /// Creates a new [SerializableStorageContainer] .
125128 ///
126129 /// The constructor initializes a container for storing serializable objects
@@ -703,6 +706,9 @@ abstract class SerializableStorageContainer<E extends Object> extends StorageCon
703706 @override
704707 Future <void > close () async {
705708 removeAllListeners ();
709+ for (final holder in _holders.values) {
710+ holder.dispose ();
711+ }
706712 await backend.close ();
707713 }
708714
@@ -714,27 +720,45 @@ abstract class SerializableStorageContainer<E extends Object> extends StorageCon
714720 /// to avoid memory leaks.
715721 ///
716722 /// This can be done by cancelling the subscription to the stream.
717- Stream <E ?> stream (String key) async * {
718- final E ? itemValue = await get (key);
719- yield itemValue;
720-
721- late final void Function () retrieveAndAdd;
722- final controller = StreamController <E ?>(
723- onCancel: () => removeKeyListener (key, retrieveAndAdd),
724- );
725-
726- retrieveAndAdd = () async {
727- if (controller.isClosed) return ;
728- final E ? value = await get (key);
729- if (! controller.isClosed) {
730- controller.add (value);
731- }
732- };
723+ Stream <E ?> stream (String key) => itemHolder (key);
733724
734- addKeyListener (key, retrieveAndAdd);
725+ /// Creates an item holder for storing a single primitive value at the specified key.
726+ ///
727+ /// [ItemHolder] provides a convenient API for managing a single item/key in the storage/container.
728+ /// This makes it easier to pass around a fragment of the storage (without needing to reference
729+ /// the entire storage/container) that knows how perform operations on that specific key.
730+ ///
731+ /// Note: Calling this method with the same key multiple times will return
732+ /// the same instance of [JsonItemHolder] if it already exists, ensuring that
733+ /// there is only one holder per key. If the existing holder has been closed,
734+ /// a new instance will be created.
735+ ///
736+ /// Parameters:
737+ /// - [key] : The key under which to store the item. Must be non-empty and not only whitespace.
738+ /// This key is relative to the container (not encoded).
739+ ///
740+ /// Returns:
741+ /// A [ItemHolder] configured to manage the item at the specified key.
742+ ///
743+ /// Throws:
744+ /// - [ArgumentError] if the key is invalid.
745+ SerializableItemHolder <E > itemHolder (String key) {
746+ validateKey (key);
747+ var existing = _holders[key];
748+ if (existing != null && existing.isClosed) {
749+ _holders.remove (key);
750+ existing = null ;
751+ }
752+ if (existing != null ) return existing;
735753
736- yield * controller.stream;
737- await controller.close (); // coverage:ignore-line
754+ final holder = SerializableItemHolder <E >(
755+ this ,
756+ encodeKey (key),
757+ serialize: serialize,
758+ deserialize: deserialize,
759+ );
760+ _holders[key] = holder;
761+ return holder;
738762 }
739763
740764 /// Provides a [Stream] of values for all items in the container.
0 commit comments