11import 'dart:async' ;
22
33import 'package:flutter/foundation.dart' ;
4- import 'package:hydrated/src/utils/type_utils.dart' ;
54import 'package:rxdart/rxdart.dart' ;
6- import 'package:shared_preferences/shared_preferences.dart' ;
5+
6+ import 'key_value_store/key_value_store.dart' ;
7+ import 'key_value_store/store_error.dart' ;
8+ import 'key_value_store/shared_preferences_store.dart' ;
79
810/// A callback for encoding an instance of a data class into a String.
911typedef PersistCallback <T > = String ? Function (T );
@@ -15,21 +17,8 @@ typedef HydrateCallback<T> = T Function(String);
1517///
1618/// Mimics the behavior of a [BehaviorSubject] .
1719///
18- /// HydratedSubject supports serialized classes and [shared_preferences] types
19- /// such as:
20- /// - `int`
21- /// - `double`
22- /// - `bool`
23- /// - `String`
24- /// - `List<String>` .
25- ///
26- /// Serialized classes are supported by using the following `hydrate` and
27- /// `persist` combination:
28- ///
29- /// ```
30- /// hydrate: (String)=>Class
31- /// persist: (Class)=>String
32- /// ```
20+ /// The set of supported classes depends on the [KeyValueStore] implementation.
21+ /// For a list of types supported by default see [SharedPreferencesStore] .
3322///
3423/// Example:
3524///
@@ -58,21 +47,27 @@ typedef HydrateCallback<T> = T Function(String);
5847/// );
5948/// ```
6049class HydratedSubject <T > extends Subject <T > implements ValueStream <T > {
61- static final _areTypesEqual = TypeUtils .areTypesEqual;
62- final BehaviorSubject <T > _subject;
6350 final String _key;
6451 final HydrateCallback <T >? _hydrate;
6552 final PersistCallback <T >? _persist;
53+ final BehaviorSubject <T > _subject;
6654 final VoidCallback ? _onHydrate;
6755 final T ? _seedValue;
6856
57+ final KeyValueStore _persistence;
58+
59+ /// A unique key that references a storage container
60+ /// for a value persisted on the device.
61+ String get key => _key;
62+
6963 HydratedSubject ._(
7064 this ._key,
7165 this ._seedValue,
7266 this ._hydrate,
7367 this ._persist,
7468 this ._onHydrate,
7569 this ._subject,
70+ this ._persistence,
7671 ) : super (_subject, _subject.stream) {
7772 _hydrateSubject ();
7873 }
@@ -85,22 +80,13 @@ class HydratedSubject<T> extends Subject<T> implements ValueStream<T> {
8580 VoidCallback ? onHydrate,
8681 VoidCallback ? onListen,
8782 VoidCallback ? onCancel,
88- bool sync : false ,
83+ bool sync = false ,
84+ KeyValueStore keyValueStore = const SharedPreferencesStore (),
8985 }) {
90- // assert that T is a type compatible with shared_preferences,
91- // or that we have hydrate and persist mapping functions
92- assert (_areTypesEqual <T , int >() ||
93- _areTypesEqual <T , int ?>() ||
94- _areTypesEqual <T , double >() ||
95- _areTypesEqual <T , double ?>() ||
96- _areTypesEqual <T , bool >() ||
97- _areTypesEqual <T , bool ?>() ||
98- _areTypesEqual <T , String >() ||
99- _areTypesEqual <T , String ?>() ||
100- _areTypesEqual <T , List <String >>() ||
101- _areTypesEqual <T , List <String >?>() ||
102- (hydrate != null && persist != null ));
103-
86+ assert (
87+ (hydrate == null && persist == null ) ||
88+ (hydrate != null && persist != null ),
89+ '`hydrate` and `persist` callbacks must both be present.' );
10490 // ignore: close_sinks
10591 final subject = seedValue != null
10692 ? BehaviorSubject <T >.seeded (
@@ -122,16 +108,12 @@ class HydratedSubject<T> extends Subject<T> implements ValueStream<T> {
122108 persist,
123109 onHydrate,
124110 subject,
111+ keyValueStore,
125112 );
126113 }
127114
128- /// A unique key that references a storage container
129- /// for a value persisted on the device.
130- String get key => _key;
131-
132115 @override
133116 void onAdd (T event) {
134- _subject.add (event);
135117 _persistValue (event);
136118 }
137119
@@ -149,7 +131,7 @@ class HydratedSubject<T> extends Subject<T> implements ValueStream<T> {
149131 T get value => _subject.value;
150132
151133 /// Set and emit the new value
152- set value (T newValue) => add (value );
134+ set value (T newValue) => add (newValue );
153135
154136 @override
155137 Object get error => _subject.error;
@@ -167,68 +149,43 @@ class HydratedSubject<T> extends Subject<T> implements ValueStream<T> {
167149 ///
168150 /// Must be called to retrieve values stored on the device.
169151 Future <void > _hydrateSubject () async {
170- final prefs = await SharedPreferences .getInstance ();
171-
172- T ? val;
152+ try {
153+ T ? val;
154+ final hydrate = _hydrate;
155+ if (hydrate != null ) {
156+ final persistedValue = await _persistence.get <String >(_key);
157+ if (persistedValue != null ) {
158+ val = hydrate (persistedValue);
159+ }
160+ } else {
161+ val = await _persistence.get <T ?>(_key);
162+ }
173163
174- if (_hydrate != null ) {
175- final String ? persistedValue = prefs. getString (_key);
176- if (persistedValue != null ) {
177- val = _hydrate !(persistedValue );
164+ // do not hydrate if the store is empty or matches the seed value
165+ // TODO: allow writing of seedValue if it is intentional
166+ if (val != null && val != _seedValue ) {
167+ _subject. add (val );
178168 }
179- } else if (_areTypesEqual <T , int >() || _areTypesEqual <T , int ?>())
180- val = prefs.getInt (_key) as T ? ;
181- else if (_areTypesEqual <T , double >() || _areTypesEqual <T , double ?>())
182- val = prefs.getDouble (_key) as T ? ;
183- else if (_areTypesEqual <T , bool >() || _areTypesEqual <T , bool ?>())
184- val = prefs.getBool (_key) as T ? ;
185- else if (_areTypesEqual <T , String >() || _areTypesEqual <T , String ?>())
186- val = prefs.getString (_key) as T ? ;
187- else if (_areTypesEqual <T , List <String >>() ||
188- _areTypesEqual <T , List <String >?>())
189- val = prefs.getStringList (_key) as T ? ;
190- else
191- Exception (
192- 'HydratedSubject – shared_preferences returned an invalid type' ,
193- );
194-
195- // do not hydrate if the store is empty or matches the seed value
196- // TODO: allow writing of seedValue if it is intentional
197- if (val != null && val != _seedValue) {
198- _subject.add (val);
199- }
200169
201- _onHydrate? .call ();
170+ _onHydrate? .call ();
171+ } on StoreError catch (e, s) {
172+ addError (e, s);
173+ }
202174 }
203175
204176 void _persistValue (T val) async {
205- final prefs = await SharedPreferences .getInstance ();
206-
207- if (val is int )
208- await prefs.setInt (_key, val);
209- else if (val is double )
210- await prefs.setDouble (_key, val);
211- else if (val is bool )
212- await prefs.setBool (_key, val);
213- else if (val is String )
214- await prefs.setString (_key, val);
215- else if (val is List <String >)
216- await prefs.setStringList (_key, val);
217- else if (val == null )
218- prefs.remove (_key);
219- else if (_persist != null ) {
220- final encoded = _persist !(val);
221- if (encoded != null ) {
222- await prefs.setString (_key, encoded);
177+ try {
178+ final persist = _persist;
179+ var persistedVal;
180+ if (persist != null ) {
181+ persistedVal = persist (val);
182+ await _persistence.put <String >(_key, persistedVal);
223183 } else {
224- prefs.remove (_key);
184+ persistedVal = val;
185+ await _persistence.put <T >(_key, persistedVal);
225186 }
226- } else {
227- final error = Exception (
228- 'HydratedSubject – value must be int, '
229- 'double, bool, String, or List<String>' ,
230- );
231- _subject.addError (error, StackTrace .current);
187+ } on StoreError catch (e, s) {
188+ addError (e, s);
232189 }
233190 }
234191
0 commit comments