diff --git a/packages/neon/neon/lib/src/settings/models/option.dart b/packages/neon/neon/lib/src/settings/models/option.dart index 5e9d57e9..4d33bfb3 100644 --- a/packages/neon/neon/lib/src/settings/models/option.dart +++ b/packages/neon/neon/lib/src/settings/models/option.dart @@ -84,10 +84,10 @@ abstract class Option extends ChangeNotifier implements ValueListenable { } /// Deserializes the data. - T deserialize(final Object data); + T? deserialize(final Object? data); /// Serializes the [value]. - Object serialize(); + Object? serialize(); BehaviorSubject? _stream; diff --git a/packages/neon/neon/lib/src/settings/models/select_option.dart b/packages/neon/neon/lib/src/settings/models/select_option.dart index e1d7e110..0cc0b9e2 100644 --- a/packages/neon/neon/lib/src/settings/models/select_option.dart +++ b/packages/neon/neon/lib/src/settings/models/select_option.dart @@ -46,15 +46,14 @@ class SelectOption extends Option { return stored as T; } - return _fromString(vs, stored); + return _deserialize(vs, stored); } - static T? _fromString(final Map vs, final String? valueStr) { - if (valueStr == null) { - return null; - } + @override + void reset() { + unawaited(storage.remove(key)); - return vs.keys.firstWhereOrNull((final e) => e.toString() == valueStr); + super.reset(); } Map _values; @@ -62,7 +61,10 @@ class SelectOption extends Option { @override set value(final T value) { super.value = value; - unawaited(storage.setString(key, serialize())); + + if (value != null) { + unawaited(storage.setString(key, serialize()!)); + } } /// A collection of different values this can have. @@ -80,8 +82,18 @@ class SelectOption extends Option { } @override - String serialize() => value.toString(); + String? serialize() => _serialize(value); + + static String? _serialize(final T value) => value?.toString(); @override - T deserialize(final Object data) => _fromString(_values, data as String)!; + T? deserialize(final Object? data) => _deserialize(_values, data as String?); + + static T? _deserialize(final Map vs, final String? valueStr) { + if (valueStr == null) { + return null; + } + + return vs.keys.firstWhereOrNull((final e) => _serialize(e) == valueStr); + } } diff --git a/packages/neon/neon/lib/src/settings/models/storage.dart b/packages/neon/neon/lib/src/settings/models/storage.dart index 973d13ad..7ae22cf2 100644 --- a/packages/neon/neon/lib/src/settings/models/storage.dart +++ b/packages/neon/neon/lib/src/settings/models/storage.dart @@ -9,6 +9,8 @@ abstract interface class SettingsStorage { // ignore: avoid_positional_boolean_parameters Future setBool(final String key, final bool value); + + Future remove(final String key); } class AppStorage implements SettingsStorage { @@ -24,6 +26,7 @@ class AppStorage implements SettingsStorage { bool containsKey(final String key) => _sharedPreferences.containsKey(_formatKey(key)); + @override Future remove(final String key) => _sharedPreferences.remove(_formatKey(key)); @override diff --git a/packages/neon/neon/lib/src/settings/models/toggle_option.dart b/packages/neon/neon/lib/src/settings/models/toggle_option.dart index 227f4b33..e3ef5397 100644 --- a/packages/neon/neon/lib/src/settings/models/toggle_option.dart +++ b/packages/neon/neon/lib/src/settings/models/toggle_option.dart @@ -25,9 +25,17 @@ class ToggleOption extends Option { defaultValue: storage.getBool(key) ?? defaultValue, ); + @override + void reset() { + unawaited(storage.remove(key)); + + super.reset(); + } + @override set value(final bool value) { super.value = value; + unawaited(storage.setBool(key, serialize())); } @@ -35,5 +43,5 @@ class ToggleOption extends Option { bool serialize() => value; @override - bool deserialize(final Object data) => data as bool; + bool? deserialize(final Object? data) => data as bool?; } diff --git a/packages/neon/neon/test/option_test.dart b/packages/neon/neon/test/option_test.dart index 0cadbb3e..e28fb695 100644 --- a/packages/neon/neon/test/option_test.dart +++ b/packages/neon/neon/test/option_test.dart @@ -37,6 +37,8 @@ void main() { setUp(() { when(() => storage.setString(key, any())).thenAnswer((final _) async {}); + when(() => storage.remove(key)).thenAnswer((final _) async => true); + option = SelectOption( storage: storage, key: key, @@ -148,8 +150,21 @@ void main() { option.reset(); verify(callback.call).called(1); + verify(() => storage.remove(key)).called(1); expect(option.value, option.defaultValue, reason: 'Should reset the value.'); }); + + test('Serialize null', () { + final option = SelectOption( + storage: storage, + key: key, + label: labelBuilder, + defaultValue: null, + values: valuesLabel, + ); + + expect(option.serialize(), null, reason: 'Should serialize to null. A string containing "null" is an error'); + }); }); group('ToggleOption', () { @@ -157,6 +172,8 @@ void main() { setUp(() { when(() => storage.setBool(key, any())).thenAnswer((final _) async {}); + when(() => storage.remove(key)).thenAnswer((final _) async => true); + option = ToggleOption( storage: storage, key: key, @@ -245,6 +262,7 @@ void main() { option.reset(); verify(callback.call).called(1); + verify(() => storage.remove(key)).called(1); expect(option.value, option.defaultValue, reason: 'Should reset the value.'); }); });