diff --git a/.github/workflows/flutter.yaml b/.github/workflows/flutter.yaml
index 4224495..602b53e 100644
--- a/.github/workflows/flutter.yaml
+++ b/.github/workflows/flutter.yaml
@@ -9,7 +9,6 @@ jobs:
- uses: actions/checkout@v1
- uses: subosito/flutter-action@v1
with:
- flutter-version: '1.12.13+hotfix.5'
channel: 'stable'
- run: flutter pub get
- run: flutter analyze
diff --git a/.gitignore b/.gitignore
index 446ed0d..a247422 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,75 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
.DS_Store
-.dart_tool/
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
.packages
+.pub-cache/
.pub/
-
build/
-ios/.generated/
-ios/Flutter/Generated.xcconfig
-ios/Runner/GeneratedPluginRegistrant.*
+
+# Android related
+**/android/**/gradle-wrapper.jar
+**/android/.gradle
+**/android/captures/
+**/android/gradlew
+**/android/gradlew.bat
+**/android/local.properties
+**/android/**/GeneratedPluginRegistrant.java
+
+# iOS/XCode related
+**/ios/**/*.mode1v3
+**/ios/**/*.mode2v3
+**/ios/**/*.moved-aside
+**/ios/**/*.pbxuser
+**/ios/**/*.perspectivev3
+**/ios/**/*sync/
+**/ios/**/.sconsign.dblite
+**/ios/**/.tags*
+**/ios/**/.vagrant/
+**/ios/**/DerivedData/
+**/ios/**/Icon?
+**/ios/**/Pods/
+**/ios/**/.symlinks/
+**/ios/**/profile
+**/ios/**/xcuserdata
+**/ios/.generated/
+**/ios/Flutter/App.framework
+**/ios/Flutter/Flutter.framework
+**/ios/Flutter/Flutter.podspec
+**/ios/Flutter/Generated.xcconfig
+**/ios/Flutter/ephemeral
+**/ios/Flutter/app.flx
+**/ios/Flutter/app.zip
+**/ios/Flutter/flutter_assets/
+**/ios/Flutter/flutter_export_environment.sh
+**/ios/ServiceDefinitions.json
+**/ios/Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!**/ios/**/default.mode1v3
+!**/ios/**/default.mode2v3
+!**/ios/**/default.pbxuser
+!**/ios/**/default.perspectivev3
diff --git a/.idea/libraries/Dart_SDK.xml b/.idea/libraries/Dart_SDK.xml
deleted file mode 100644
index 896efb4..0000000
--- a/.idea/libraries/Dart_SDK.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index b25ed89..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
deleted file mode 100644
index 5b3388c..0000000
--- a/.idea/workspace.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/example/lib/class_example.dart b/example/lib/class_example.dart
index 3f554ae..25bcd34 100644
--- a/example/lib/class_example.dart
+++ b/example/lib/class_example.dart
@@ -17,16 +17,20 @@ class MyApp extends StatelessWidget {
}
class MyHomePage extends StatelessWidget {
- final String title;
+ final String _title;
- final count$ = HydratedSubject(
+ final _countSubject = HydratedSubject(
"serialized-count",
hydrate: (value) => SerializedClass.fromJSON(value),
persist: (value) => value.toJSON,
seedValue: SerializedClass(0),
);
- MyHomePage({Key key, this.title}) : super(key: key);
+ MyHomePage({
+ Key? key,
+ required String title,
+ }) : _title = title,
+ super(key: key);
@override
Widget build(BuildContext context) {
@@ -34,42 +38,42 @@ class MyHomePage extends StatelessWidget {
return Scaffold(
appBar: AppBar(
- title: Text(this.title),
+ title: Text(this._title),
),
body: Center(
child: StreamBuilder(
- stream: count$,
- initialData: count$.value,
- builder: (context, snap) => Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Text(
- 'You have pushed the button this many times:',
- ),
- Text(
- '${snap.data.count}',
- style: Theme.of(context).textTheme.display1,
- ),
- ],
+ stream: _countSubject,
+ initialData: _countSubject.value,
+ builder: (context, snapshot) => Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text('You have pushed the button this many times:'),
+ Text(
+ '${snapshot.data?.count}',
+ style: Theme.of(context).textTheme.headline4,
),
+ ],
+ ),
),
),
floatingActionButton: FloatingActionButton(
- onPressed: () {
- final count = count$.value.count + 1;
- count$.add(SerializedClass(count));
- },
+ onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
+
+ void _incrementCounter() {
+ final count = _countSubject.value.count + 1;
+ _countSubject.add(SerializedClass(count));
+ }
}
class SerializedClass {
final int count;
- SerializedClass(this.count);
+ const SerializedClass(this.count);
SerializedClass.fromJSON(String json) : this.count = int.parse(json);
diff --git a/example/lib/main.dart b/example/lib/main.dart
index bdbe909..759e904 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -17,42 +17,50 @@ class MyApp extends StatelessWidget {
}
class MyHomePage extends StatelessWidget {
- final String title;
- final count$ = HydratedSubject("count", seedValue: 0);
+ final String _title;
+ final _count = HydratedSubject("count", seedValue: 0);
- MyHomePage({Key key, this.title}) : super(key: key);
+ MyHomePage({
+ Key? key,
+ required String title,
+ }) : _title = title,
+ super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
- title: Text(this.title),
+ title: Text(this._title),
),
body: Center(
child: StreamBuilder(
- stream: count$,
- initialData: count$.value,
+ stream: _count,
+ initialData: _count.value,
builder: (context, snap) => Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Text(
- 'You have pushed the button this many times:',
- ),
- Text(
- '${snap.data}',
- style: Theme.of(context).textTheme.display1,
- ),
- ],
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text('You have pushed the button this many times:'),
+ Text(
+ '${snap.data}',
+ style: Theme.of(context).textTheme.headline4,
),
+ ],
+ ),
),
),
floatingActionButton: FloatingActionButton(
- onPressed: () => count$.value++,
+ onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
- void dispose() => count$.close();
+ void _incrementCounter() {
+ _count.value++;
+ }
+
+ void dispose() {
+ _count.close();
+ }
}
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index 96458b6..92d0015 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -1,65 +1,22 @@
name: hydrated_demo
description: A demo showcasing the Hydrated package.
+publish_to: 'none'
-# The following defines the version and build number for your application.
-# A version number is three numbers separated by dots, like 1.2.43
-# followed by an optional build number separated by a +.
-# Both the version and the builder number may be overridden in flutter
-# build by specifying --build-name and --build-number, respectively.
-# Read more about versioning at semver.org.
version: 1.0.0+1
environment:
- sdk: ">=2.6.0 <3.0.0"
+ sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
- # The following adds the Cupertino Icons font to your application.
- # Use with the CupertinoIcons class for iOS style icons.
- cupertino_icons: ^0.1.2
hydrated:
path: ../
-
dev_dependencies:
flutter_test:
sdk: flutter
-# For information on the generic Dart part of this file, see the
-# following page: https://www.dartlang.org/tools/pub/pubspec
-
-# The following section is specific to Flutter.
flutter:
- # The following line ensures that the Material Icons font is
- # included with your application, so that you can use the icons in
- # the material Icons class.
uses-material-design: true
- # To add assets to your application, add an assets section, like this:
- # assets:
- # - images/a_dot_burr.jpeg
- # - images/a_dot_ham.jpeg
- # An image asset can refer to one or more resolution-specific "variants", see
- # https://flutter.io/assets-and-images/#resolution-aware.
- # For details regarding adding assets from package dependencies, see
- # https://flutter.io/assets-and-images/#from-packages
- # To add custom fonts to your application, add a fonts section here,
- # in this "flutter" section. Each entry in this list should have a
- # "family" key with the font family name, and a "fonts" key with a
- # list giving the asset and other descriptors for the font. For
- # example:
- # fonts:
- # - family: Schyler
- # fonts:
- # - asset: fonts/Schyler-Regular.ttf
- # - asset: fonts/Schyler-Italic.ttf
- # style: italic
- # - family: Trajan Pro
- # fonts:
- # - asset: fonts/TrajanPro.ttf
- # - asset: fonts/TrajanPro_Bold.ttf
- # weight: 700
- #
- # For details regarding fonts from package dependencies,
- # see https://flutter.io/custom-fonts/#from-packages
diff --git a/hydrated.iml b/hydrated.iml
deleted file mode 100644
index 8d48a06..0000000
--- a/hydrated.iml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/lib/hydrated.dart b/lib/hydrated.dart
index ba0fc62..4a3011e 100644
--- a/lib/hydrated.dart
+++ b/lib/hydrated.dart
@@ -1,189 +1,3 @@
library hydrated;
-import 'dart:async';
-import 'package:shared_preferences/shared_preferences.dart';
-import 'package:rxdart/rxdart.dart';
-
-/// A [Subject] that automatically persists its values and hydrates on creation.
-///
-/// HydratedSubject supports serialized classes and [shared_preferences] types such as: `int`, `double`, `bool`, `String`, and `List`
-///
-/// Serialized classes are supported by using the `hydrate: (String)=>Class` and `persist: (Class)=>String` constructor arguments.
-///
-/// Example:
-///
-/// ```
-/// final count$ = HydratedSubject("count", seedValue: 0);
-/// ```
-///
-/// Serialized class example:
-///
-/// ```
-/// final user$ = HydratedSubject(
-/// "user",
-/// hydrate: (String s) => User.fromJSON(s),
-/// persist: (User user) => user.toJSON(),
-/// seedValue: User.empty(),
-/// );
-/// ```
-///
-/// Hydration is performed automatically and is asynchronous. The `onHydrate` callback is called when hydration is complete.
-///
-/// ```
-/// final user$ = HydratedSubject(
-/// "count",
-/// onHydrate: () => loading$.add(false),
-/// );
-/// ```
-
-class HydratedSubject extends Subject implements ValueStream {
- String _key;
- T _seedValue;
- _Wrapper _wrapper;
-
- T Function(String value) _hydrate;
- String Function(T value) _persist;
- void Function() _onHydrate;
-
- HydratedSubject._(
- this._key,
- this._seedValue,
- this._hydrate,
- this._persist,
- this._onHydrate,
- StreamController controller,
- Stream observable,
- this._wrapper,
- ) : super(controller, observable) {
- _hydrateSubject();
- }
-
- factory HydratedSubject(
- String key, {
- T seedValue,
- T Function(String value) hydrate,
- String Function(T value) persist,
- void onHydrate(),
- void onListen(),
- void onCancel(),
- bool sync: false,
- }) {
- // assert that T is a type compatible with shared_preferences,
- // or that we have hydrate and persist mapping functions
- assert(T == int ||
- T == double ||
- T == bool ||
- T == String ||
- [""] is T ||
- (hydrate != null && persist != null));
-
- // ignore: close_sinks
- final controller = new StreamController.broadcast(
- onListen: onListen,
- onCancel: onCancel,
- sync: sync,
- );
-
- final wrapper = new _Wrapper(seedValue);
-
- return new HydratedSubject._(
- key,
- seedValue,
- hydrate,
- persist,
- onHydrate,
- controller,
- Rx.defer(
- () => wrapper.latestValue == null
- ? controller.stream
- : controller.stream
- .startWith(wrapper.latestValue),
- reusable: true),
- wrapper);
- }
-
- @override
- void onAdd(T event) {
- _wrapper.latestValue = event;
- _persistValue(event);
- }
-
- @override
- ValueStream get stream => this;
-
- /// Get the latest value emitted by the Subject
- @override
- T get value => _wrapper.latestValue;
-
- /// Set and emit the new value
- set value(T newValue) => add(newValue);
-
- @override
- bool get hasValue => _wrapper.latestValue != null;
-
- /// Hydrates the HydratedSubject with a value stored on the user's device.
- ///
- /// Must be called to retreive values stored on the device.
- Future _hydrateSubject() async {
- final prefs = await SharedPreferences.getInstance();
-
- var val;
-
- if (this._hydrate != null)
- val = this._hydrate(prefs.getString(this._key));
- else if (T == int)
- val = prefs.getInt(this._key);
- else if (T == double)
- val = prefs.getDouble(this._key);
- else if (T == bool)
- val = prefs.getBool(this._key);
- else if (T == String)
- val = prefs.getString(this._key);
- else if ([""] is T)
- val = prefs.getStringList(this._key);
- else
- Exception(
- "HydratedSubject – shared_preferences returned an invalid type",
- );
-
- // do not hydrate if the store is empty or matches the seed value
- // TODO: allow writing of seedValue if it is intentional
- if (val != null && val != _seedValue) {
- add(val);
- }
-
- if (_onHydrate != null) {
- this._onHydrate();
- }
- }
-
- _persistValue(T val) async {
- final prefs = await SharedPreferences.getInstance();
-
- if (val is int)
- await prefs.setInt(_key, val);
- else if (val is double)
- await prefs.setDouble(_key, val);
- else if (val is bool)
- await prefs.setBool(_key, val);
- else if (val is String)
- await prefs.setString(_key, val);
- else if (val is List)
- await prefs.setStringList(_key, val);
- else if (this._persist != null)
- await prefs.setString(_key, this._persist(val));
- else
- Exception(
- "HydratedSubject – value must be int, double, bool, String, or List",
- );
- }
-
- /// A unique key that references a storage container for a value persisted on the device.
- String get key => this._key;
-}
-
-class _Wrapper {
- T latestValue;
-
- _Wrapper(this.latestValue);
-}
+export 'src/hydrated.dart';
\ No newline at end of file
diff --git a/lib/src/hydrated.dart b/lib/src/hydrated.dart
new file mode 100644
index 0000000..117c009
--- /dev/null
+++ b/lib/src/hydrated.dart
@@ -0,0 +1,263 @@
+import 'dart:async';
+
+import 'package:flutter/foundation.dart';
+import 'package:hydrated/src/utils/typing_utils.dart';
+import 'package:rxdart/rxdart.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+import 'model/subject_value_wrapper.dart';
+
+/// A callback for encoding an instance of a data class into a String.
+typedef PersistCallback = String? Function(T);
+
+/// A callback for reconstructing an instance of a data class from a String.
+typedef HydrateCallback = T Function(String);
+
+/// A [Subject] that automatically persists its values and hydrates on creation.
+///
+/// HydratedSubject supports serialized classes and [shared_preferences] types
+/// such as:
+/// - `int`
+/// - `double`
+/// - `bool`
+/// - `String`
+/// - `List`.
+///
+/// Serialized classes are supported by using the following `hydrate` and
+/// `persist` combination:
+///
+/// ```
+/// hydrate: (String)=>Class
+/// persist: (Class)=>String
+/// ```
+///
+/// Example:
+///
+/// ```
+/// final count = HydratedSubject("count", seedValue: 0);
+/// ```
+///
+/// Serialized class example:
+///
+/// ```
+/// final user = HydratedSubject(
+/// "user",
+/// hydrate: (String s) => User.fromJSON(s),
+/// persist: (User user) => user.toJSON(),
+/// seedValue: User.empty(),
+/// );
+/// ```
+///
+/// Hydration is performed automatically and is asynchronous.
+/// The `onHydrate` callback is called when hydration is complete.
+///
+/// ```
+/// final user = HydratedSubject(
+/// "count",
+/// onHydrate: () => loading.add(false),
+/// );
+/// ```
+class HydratedSubject extends Subject implements ValueStream {
+ static final _areTypesEqual = TypeUtils.areTypesEqual;
+
+ final String _key;
+ final HydrateCallback? _hydrate;
+ final PersistCallback? _persist;
+ final VoidCallback? _onHydrate;
+ final T? _seedValue;
+ final StreamController _controller;
+
+ SubjectValueWrapper? _wrapper;
+
+ HydratedSubject._(
+ this._key,
+ this._seedValue,
+ this._hydrate,
+ this._persist,
+ this._onHydrate,
+ this._controller,
+ Stream observable,
+ this._wrapper,
+ ) : super(_controller, observable) {
+ _hydrateSubject();
+ }
+
+ factory HydratedSubject(
+ String key, {
+ T? seedValue,
+ HydrateCallback? hydrate,
+ PersistCallback? persist,
+ VoidCallback? onHydrate,
+ VoidCallback? onListen,
+ VoidCallback? onCancel,
+ bool sync: false,
+ }) {
+ // assert that T is a type compatible with shared_preferences,
+ // or that we have hydrate and persist mapping functions
+ assert(_areTypesEqual() ||
+ _areTypesEqual() ||
+ _areTypesEqual() ||
+ _areTypesEqual() ||
+ _areTypesEqual() ||
+ _areTypesEqual() ||
+ _areTypesEqual() ||
+ _areTypesEqual() ||
+ _areTypesEqual>() ||
+ _areTypesEqual?>() ||
+ (hydrate != null && persist != null));
+
+ // ignore: close_sinks
+ final StreamController controller = StreamController.broadcast(
+ onListen: onListen,
+ onCancel: onCancel,
+ sync: sync,
+ );
+
+ final wrapper =
+ seedValue != null ? SubjectValueWrapper(value: seedValue) : null;
+
+ return HydratedSubject._(
+ key,
+ seedValue,
+ hydrate,
+ persist,
+ onHydrate,
+ controller,
+ Rx.defer(
+ () => wrapper == null
+ ? controller.stream
+ : controller.stream.startWith(wrapper.value!),
+ reusable: true),
+ wrapper);
+ }
+
+ /// A unique key that references a storage container
+ /// for a value persisted on the device.
+ String get key => _key;
+
+ @override
+ void onAdd(T event) {
+ _wrapper = SubjectValueWrapper(value: event);
+ _persistValue(event);
+ }
+
+ @override
+ ValueStream get stream => this;
+
+ @override
+ bool get hasValue => _wrapper?.value != null;
+
+ @override
+ T? get valueOrNull => _wrapper?.value;
+
+ /// Get the latest value emitted by the Subject
+ @override
+ T get value =>
+ hasValue ? _wrapper!.value! : throw ValueStreamError.hasNoValue();
+
+ /// Set and emit the new value
+ set value(T newValue) => add(newValue);
+
+ @override
+ Object get error => hasError
+ ? _wrapper!.errorAndStackTrace!
+ : throw ValueStreamError.hasNoError();
+
+ @override
+ Object? get errorOrNull => _wrapper?.errorAndStackTrace;
+
+ @override
+ bool get hasError => _wrapper?.errorAndStackTrace != null;
+
+ @override
+ StackTrace? get stackTrace => _wrapper?.errorAndStackTrace?.stackTrace;
+
+ /// Hydrates the HydratedSubject with a value stored on the user's device.
+ ///
+ /// Must be called to retrieve values stored on the device.
+ Future _hydrateSubject() async {
+ final prefs = await SharedPreferences.getInstance();
+
+ T? val;
+
+ if (_hydrate != null) {
+ final String? persistedValue = prefs.getString(_key);
+ if (persistedValue != null) {
+ val = _hydrate!(persistedValue);
+ }
+ } else if (_areTypesEqual() || _areTypesEqual())
+ val = prefs.getInt(_key) as T?;
+ else if (_areTypesEqual() || _areTypesEqual())
+ val = prefs.getDouble(_key) as T?;
+ else if (_areTypesEqual() || _areTypesEqual())
+ val = prefs.getBool(_key) as T?;
+ else if (_areTypesEqual() || _areTypesEqual())
+ val = prefs.getString(_key) as T?;
+ else if (_areTypesEqual>() ||
+ _areTypesEqual?>())
+ val = prefs.getStringList(_key) as T?;
+ else
+ Exception(
+ 'HydratedSubject – shared_preferences returned an invalid type',
+ );
+
+ // do not hydrate if the store is empty or matches the seed value
+ // TODO: allow writing of seedValue if it is intentional
+ if (val != null && val != _seedValue) {
+ _wrapper = SubjectValueWrapper(value: val);
+ _controller.add(val);
+ }
+
+ _onHydrate?.call();
+ }
+
+ void _persistValue(T val) async {
+ final prefs = await SharedPreferences.getInstance();
+
+ if (val is int)
+ await prefs.setInt(_key, val);
+ else if (val is double)
+ await prefs.setDouble(_key, val);
+ else if (val is bool)
+ await prefs.setBool(_key, val);
+ else if (val is String)
+ await prefs.setString(_key, val);
+ else if (val is List)
+ await prefs.setStringList(_key, val);
+ else if (val == null)
+ prefs.remove(_key);
+ else if (_persist != null) {
+ final encoded = _persist!(val);
+ if (encoded != null) {
+ await prefs.setString(_key, encoded);
+ } else {
+ prefs.remove(_key);
+ }
+ } else {
+ final error = Exception(
+ 'HydratedSubject – value must be int, '
+ 'double, bool, String, or List',
+ );
+ final errorAndTrace = ErrorAndStackTrace(error, StackTrace.current);
+ _wrapper = SubjectValueWrapper(errorAndStackTrace: errorAndTrace);
+ }
+ }
+
+ @override
+ Subject createForwardingSubject({
+ VoidCallback? onListen,
+ VoidCallback? onCancel,
+ bool sync = false,
+ HydrateCallback? hydrate,
+ PersistCallback? persist,
+ }) {
+ return HydratedSubject(
+ _key,
+ onListen: onListen,
+ onCancel: onCancel,
+ sync: sync,
+ hydrate: hydrate,
+ persist: persist,
+ );
+ }
+}
diff --git a/lib/src/model/subject_value_wrapper.dart b/lib/src/model/subject_value_wrapper.dart
new file mode 100644
index 0000000..2ffa359
--- /dev/null
+++ b/lib/src/model/subject_value_wrapper.dart
@@ -0,0 +1,11 @@
+import 'package:rxdart/rxdart.dart';
+
+class SubjectValueWrapper {
+ final T? value;
+ final ErrorAndStackTrace? errorAndStackTrace;
+
+ SubjectValueWrapper({
+ this.value,
+ this.errorAndStackTrace,
+ });
+}
diff --git a/lib/src/utils/typing_utils.dart b/lib/src/utils/typing_utils.dart
new file mode 100644
index 0000000..df0c94c
--- /dev/null
+++ b/lib/src/utils/typing_utils.dart
@@ -0,0 +1,5 @@
+class TypeUtils {
+ static bool areTypesEqual() {
+ return T1 == T2;
+ }
+}
diff --git a/pubspec.lock b/pubspec.lock
index 0372fa6..15174fd 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -7,42 +7,63 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
- version: "2.4.1"
+ version: "2.6.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.0"
+ version: "2.1.0"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.3"
+ version: "1.2.0"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
- version: "1.0.1"
+ version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
- version: "1.14.12"
+ version: "1.15.0"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0"
+ version: "1.2.0"
+ ffi:
+ dependency: transitive
+ description:
+ name: ffi
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.0"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "6.1.0"
flutter:
dependency: "direct main"
description: flutter
@@ -58,62 +79,125 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ js:
+ dependency: transitive
+ description:
+ name: js
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.6.3"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
- version: "0.12.6"
+ version: "0.12.10"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.8"
+ version: "1.3.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
- version: "1.7.0"
+ version: "1.8.0"
+ path_provider_linux:
+ dependency: transitive
+ description:
+ name: path_provider_linux
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.0"
+ path_provider_platform_interface:
+ dependency: transitive
+ description:
+ name: path_provider_platform_interface
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.0"
+ path_provider_windows:
+ dependency: transitive
+ description:
+ name: path_provider_windows
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.0"
+ platform:
+ dependency: transitive
+ description:
+ name: platform
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "3.0.0"
+ plugin_platform_interface:
+ dependency: transitive
+ description:
+ name: plugin_platform_interface
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.0"
+ process:
+ dependency: transitive
+ description:
+ name: process
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "4.1.0"
rxdart:
dependency: "direct main"
description:
name: rxdart
url: "https://pub.dartlang.org"
source: hosted
- version: "0.24.1"
+ version: "0.27.1"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
url: "https://pub.dartlang.org"
source: hosted
- version: "0.5.7+3"
+ version: "2.0.1"
+ shared_preferences_linux:
+ dependency: transitive
+ description:
+ name: shared_preferences_linux
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.0"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.dartlang.org"
source: hosted
- version: "0.0.1+3"
+ version: "2.0.0"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
url: "https://pub.dartlang.org"
source: hosted
- version: "1.0.1"
+ version: "2.0.0"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
url: "https://pub.dartlang.org"
source: hosted
- version: "0.1.2+2"
+ version: "2.0.0"
+ shared_preferences_windows:
+ dependency: transitive
+ description:
+ name: shared_preferences_windows
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.0"
sky_engine:
dependency: transitive
description: flutter
@@ -125,56 +209,70 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
- version: "1.7.0"
+ version: "1.8.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
- version: "1.9.3"
+ version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.0"
+ version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
- version: "1.0.5"
+ version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0"
+ version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
- version: "0.2.16"
+ version: "0.3.0"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.6"
+ version: "1.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.8"
+ version: "2.1.0"
+ win32:
+ dependency: transitive
+ description:
+ name: win32
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.0"
+ xdg_directories:
+ dependency: transitive
+ description:
+ name: xdg_directories
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.2.0"
sdks:
- dart: ">=2.7.0 <3.0.0"
- flutter: ">=1.12.13+hotfix.5 <2.0.0"
+ dart: ">=2.12.0 <3.0.0"
+ flutter: ">=1.20.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index 4ec9243..7587f08 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,17 +1,17 @@
name: hydrated
description: An automatically persisted BehaviorSubject with simple hydration for Flutter. Intended to be used with the BLoC pattern.
-version: 1.2.5+2
-homepage: https://github.com/lukepighetti/hydrated
+version: 1.3.0-nullsafety.1
+homepage: https://github.com/solid-software/hydrated
environment:
- sdk: ">=2.6.0 <3.0.0"
+ sdk: '>=2.12.0 <3.0.0'
dependencies:
flutter:
sdk: flutter
- rxdart: ^0.24.1
- shared_preferences: ^0.5.7+3
+ rxdart: ^0.27.0
+ shared_preferences: ^2.0.1
dev_dependencies:
flutter_test:
diff --git a/test/hydrated_test.dart b/test/hydrated_test.dart
index 3fe1e5b..074f145 100644
--- a/test/hydrated_test.dart
+++ b/test/hydrated_test.dart
@@ -1,11 +1,9 @@
import 'dart:async';
-
-import 'package:flutter_test/flutter_test.dart';
-
-import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
+import 'package:flutter_test/flutter_test.dart';
import 'package:hydrated/hydrated.dart';
+import 'package:shared_preferences/shared_preferences.dart';
void main() {
SharedPreferences.setMockInitialValues({
@@ -22,7 +20,7 @@ void main() {
final prefs = await SharedPreferences.getInstance();
final value = prefs.getBool("prefs");
- expect(value, equals(true));
+ expect(value, isTrue);
});
test('int', () async {
@@ -58,16 +56,16 @@ void main() {
final second = SerializedClass(false, 42);
/// null before hydrate
- expect(subject.value, equals(null));
+ expect(subject.valueOrNull, isNull);
/// properly hydrates
await completer.future;
- expect(subject.value.value, equals(true));
+ expect(subject.value.value, isTrue);
expect(subject.value.count, equals(42));
/// add values
subject.add(second);
- expect(subject.value.value, equals(false));
+ expect(subject.value.value, isFalse);
expect(subject.value.count, equals(42));
/// check value in store
@@ -81,21 +79,23 @@ void main() {
/// An example of a class that serializes to and from a string
class SerializedClass {
- bool value;
- int count;
+ final bool value;
+ final int count;
SerializedClass(this.value, this.count);
- SerializedClass.fromJSON(String s) {
+ factory SerializedClass.fromJSON(String s) {
final map = jsonDecode(s);
- this.value = map['value'];
- this.count = map['count'];
+ return SerializedClass(
+ map['value'],
+ map['count'],
+ );
}
String toJSON() => jsonEncode({
- "value": this.value,
- "count": this.count,
+ 'value': this.value,
+ 'count': this.count,
});
}
@@ -113,18 +113,18 @@ Future testHydrated(
);
/// null before hydrate
- expect(subject.value, equals(null));
- expect(subject.hasValue, equals(false));
+ expect(subject.valueOrNull, isNull);
+ expect(subject.hasValue, isFalse);
/// properly hydrates
await completer.future;
expect(subject.value, equals(first));
- expect(subject.hasValue, equals(true));
+ expect(subject.hasValue, isTrue);
/// add values
subject.add(second);
expect(subject.value, equals(second));
- expect(subject.hasValue, equals(true));
+ expect(subject.hasValue, isTrue);
/// check value in store
final prefs = await SharedPreferences.getInstance();