[shared_preferences] allow custom key prefixes - platform changes (#3596)
[shared_preferences] allow custom key prefixes - platform changes
diff --git a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md
index adefa4f..044aedf 100644
--- a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md
+++ b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.1.0
+
+* Adds `getAllWithPrefix` and `clearWithPrefix` methods.
+
## 2.0.17
* Clarifies explanation of endorsement in README.
diff --git a/packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/MethodCallHandlerImpl.java b/packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/MethodCallHandlerImpl.java
index cea3f34..7bdc8e7 100644
--- a/packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/MethodCallHandlerImpl.java
+++ b/packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/MethodCallHandlerImpl.java
@@ -106,16 +106,18 @@
// We've been committing the whole time.
result.success(true);
break;
- case "getAll":
- result.success(getAllPrefs());
+ case "getAllWithPrefix":
+ String prefix = call.argument("prefix");
+ result.success(getAllPrefs(prefix));
return;
case "remove":
commitAsync(preferences.edit().remove(key), result);
break;
- case "clear":
- Set<String> keySet = getAllPrefs().keySet();
+ case "clearWithPrefix":
+ String newPrefix = call.argument("prefix");
+ Set<String> keys = getAllPrefs(newPrefix).keySet();
SharedPreferences.Editor clearEditor = preferences.edit();
- for (String keyToDelete : keySet) {
+ for (String keyToDelete : keys) {
clearEditor.remove(keyToDelete);
}
commitAsync(clearEditor, result);
@@ -181,12 +183,12 @@
}
}
- // Filter preferences to only those set by the flutter app.
- private Map<String, Object> getAllPrefs() throws IOException {
+ // Gets all shared preferences, filtered to only those set with the given prefix.
+ private Map<String, Object> getAllPrefs(String prefix) throws IOException {
Map<String, ?> allPrefs = preferences.getAll();
Map<String, Object> filteredPrefs = new HashMap<>();
for (String key : allPrefs.keySet()) {
- if (key.startsWith("flutter.")) {
+ if (key.startsWith(prefix)) {
Object value = allPrefs.get(key);
if (value instanceof String) {
String stringValue = (String) value;
diff --git a/packages/shared_preferences/shared_preferences_android/example/integration_test/shared_preferences_test.dart b/packages/shared_preferences/shared_preferences_android/example/integration_test/shared_preferences_test.dart
index 4d4a85a..46ce5e4 100644
--- a/packages/shared_preferences/shared_preferences_android/example/integration_test/shared_preferences_test.dart
+++ b/packages/shared_preferences/shared_preferences_android/example/integration_test/shared_preferences_test.dart
@@ -11,22 +11,36 @@
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('SharedPreferencesAndroid', () {
- const Map<String, Object> kTestValues = <String, Object>{
+ const Map<String, Object> flutterTestValues = <String, Object>{
'flutter.String': 'hello world',
- 'flutter.bool': true,
- 'flutter.int': 42,
- 'flutter.double': 3.14159,
- 'flutter.List': <String>['foo', 'bar'],
+ 'flutter.Bool': true,
+ 'flutter.Int': 42,
+ 'flutter.Double': 3.14159,
+ 'flutter.StringList': <String>['foo', 'bar'],
};
- const Map<String, Object> kTestValues2 = <String, Object>{
- 'flutter.String': 'goodbye world',
- 'flutter.bool': false,
- 'flutter.int': 1337,
- 'flutter.double': 2.71828,
- 'flutter.List': <String>['baz', 'quox'],
+ const Map<String, Object> prefixTestValues = <String, Object>{
+ 'prefix.String': 'hello world',
+ 'prefix.Bool': true,
+ 'prefix.Int': 42,
+ 'prefix.Double': 3.14159,
+ 'prefix.StringList': <String>['foo', 'bar'],
};
+ const Map<String, Object> nonPrefixTestValues = <String, Object>{
+ 'String': 'hello world',
+ 'Bool': true,
+ 'Int': 42,
+ 'Double': 3.14159,
+ 'StringList': <String>['foo', 'bar'],
+ };
+
+ final Map<String, Object> allTestValues = <String, Object>{};
+
+ allTestValues.addAll(flutterTestValues);
+ allTestValues.addAll(prefixTestValues);
+ allTestValues.addAll(nonPrefixTestValues);
+
late SharedPreferencesStorePlatform preferences;
setUp(() async {
@@ -34,95 +48,227 @@
});
tearDown(() {
- preferences.clear();
+ preferences.clearWithPrefix('');
});
- // Normally the app-facing package adds the prefix, but since this test
- // bypasses the app-facing package it needs to be manually added.
- String prefixedKey(String key) {
- return 'flutter.$key';
- }
-
testWidgets('reading', (WidgetTester _) async {
- final Map<String, Object> values = await preferences.getAll();
- expect(values[prefixedKey('String')], isNull);
- expect(values[prefixedKey('bool')], isNull);
- expect(values[prefixedKey('int')], isNull);
- expect(values[prefixedKey('double')], isNull);
- expect(values[prefixedKey('List')], isNull);
+ final Map<String, Object> values = await preferences.getAllWithPrefix('');
+ expect(values['String'], isNull);
+ expect(values['Bool'], isNull);
+ expect(values['Int'], isNull);
+ expect(values['Double'], isNull);
+ expect(values['StringList'], isNull);
});
- testWidgets('writing', (WidgetTester _) async {
+ testWidgets('getAllWithPrefix', (WidgetTester _) async {
await Future.wait(<Future<bool>>[
preferences.setValue(
- 'String', prefixedKey('String'), kTestValues2['flutter.String']!),
+ 'String', 'prefix.String', allTestValues['prefix.String']!),
preferences.setValue(
- 'Bool', prefixedKey('bool'), kTestValues2['flutter.bool']!),
+ 'Bool', 'prefix.Bool', allTestValues['prefix.Bool']!),
+ preferences.setValue('Int', 'prefix.Int', allTestValues['prefix.Int']!),
preferences.setValue(
- 'Int', prefixedKey('int'), kTestValues2['flutter.int']!),
+ 'Double', 'prefix.Double', allTestValues['prefix.Double']!),
+ preferences.setValue('StringList', 'prefix.StringList',
+ allTestValues['prefix.StringList']!),
preferences.setValue(
- 'Double', prefixedKey('double'), kTestValues2['flutter.double']!),
+ 'String', 'flutter.String', allTestValues['flutter.String']!),
preferences.setValue(
- 'StringList', prefixedKey('List'), kTestValues2['flutter.List']!)
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!),
+ preferences.setValue(
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!),
+ preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!),
+ preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!)
]);
- final Map<String, Object> values = await preferences.getAll();
- expect(values[prefixedKey('String')], kTestValues2['flutter.String']);
- expect(values[prefixedKey('bool')], kTestValues2['flutter.bool']);
- expect(values[prefixedKey('int')], kTestValues2['flutter.int']);
- expect(values[prefixedKey('double')], kTestValues2['flutter.double']);
- expect(values[prefixedKey('List')], kTestValues2['flutter.List']);
+ final Map<String, Object> values =
+ await preferences.getAllWithPrefix('prefix.');
+ expect(values['prefix.String'], allTestValues['prefix.String']);
+ expect(values['prefix.Bool'], allTestValues['prefix.Bool']);
+ expect(values['prefix.Int'], allTestValues['prefix.Int']);
+ expect(values['prefix.Double'], allTestValues['prefix.Double']);
+ expect(values['prefix.StringList'], allTestValues['prefix.StringList']);
});
- testWidgets('removing', (WidgetTester _) async {
- final String key = prefixedKey('testKey');
- await preferences.setValue('String', key, kTestValues['flutter.String']!);
- await preferences.setValue('Bool', key, kTestValues['flutter.bool']!);
- await preferences.setValue('Int', key, kTestValues['flutter.int']!);
- await preferences.setValue('Double', key, kTestValues['flutter.double']!);
- await preferences.setValue(
- 'StringList', key, kTestValues['flutter.List']!);
- await preferences.remove(key);
+ testWidgets('clearWithPrefix', (WidgetTester _) async {
+ await Future.wait(<Future<bool>>[
+ preferences.setValue(
+ 'String', 'prefix.String', allTestValues['prefix.String']!),
+ preferences.setValue(
+ 'Bool', 'prefix.Bool', allTestValues['prefix.Bool']!),
+ preferences.setValue('Int', 'prefix.Int', allTestValues['prefix.Int']!),
+ preferences.setValue(
+ 'Double', 'prefix.Double', allTestValues['prefix.Double']!),
+ preferences.setValue('StringList', 'prefix.StringList',
+ allTestValues['prefix.StringList']!),
+ preferences.setValue(
+ 'String', 'flutter.String', allTestValues['flutter.String']!),
+ preferences.setValue(
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!),
+ preferences.setValue(
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!),
+ preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!),
+ preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!)
+ ]);
+ await preferences.clearWithPrefix('prefix.');
+ Map<String, Object> values =
+ await preferences.getAllWithPrefix('prefix.');
+ expect(values['prefix.String'], null);
+ expect(values['prefix.Bool'], null);
+ expect(values['prefix.Int'], null);
+ expect(values['prefix.Double'], null);
+ expect(values['prefix.StringList'], null);
+ values = await preferences.getAllWithPrefix('flutter.');
+ expect(values['flutter.String'], allTestValues['flutter.String']);
+ expect(values['flutter.Bool'], allTestValues['flutter.Bool']);
+ expect(values['flutter.Int'], allTestValues['flutter.Int']);
+ expect(values['flutter.Double'], allTestValues['flutter.Double']);
+ expect(values['flutter.StringList'], allTestValues['flutter.StringList']);
+ });
+
+ testWidgets('getAllWithNoPrefix', (WidgetTester _) async {
+ await Future.wait(<Future<bool>>[
+ preferences.setValue('String', 'String', allTestValues['String']!),
+ preferences.setValue('Bool', 'Bool', allTestValues['Bool']!),
+ preferences.setValue('Int', 'Int', allTestValues['Int']!),
+ preferences.setValue('Double', 'Double', allTestValues['Double']!),
+ preferences.setValue(
+ 'StringList', 'StringList', allTestValues['StringList']!),
+ preferences.setValue(
+ 'String', 'flutter.String', allTestValues['flutter.String']!),
+ preferences.setValue(
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!),
+ preferences.setValue(
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!),
+ preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!),
+ preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!)
+ ]);
+ final Map<String, Object> values = await preferences.getAllWithPrefix('');
+ expect(values['String'], allTestValues['String']);
+ expect(values['Bool'], allTestValues['Bool']);
+ expect(values['Int'], allTestValues['Int']);
+ expect(values['Double'], allTestValues['Double']);
+ expect(values['StringList'], allTestValues['StringList']);
+ expect(values['flutter.String'], allTestValues['flutter.String']);
+ expect(values['flutter.Bool'], allTestValues['flutter.Bool']);
+ expect(values['flutter.Int'], allTestValues['flutter.Int']);
+ expect(values['flutter.Double'], allTestValues['flutter.Double']);
+ expect(values['flutter.StringList'], allTestValues['flutter.StringList']);
+ });
+
+ testWidgets('clearWithNoPrefix', (WidgetTester _) async {
+ await Future.wait(<Future<bool>>[
+ preferences.setValue('String', 'String', allTestValues['String']!),
+ preferences.setValue('Bool', 'Bool', allTestValues['Bool']!),
+ preferences.setValue('Int', 'Int', allTestValues['Int']!),
+ preferences.setValue('Double', 'Double', allTestValues['Double']!),
+ preferences.setValue(
+ 'StringList', 'StringList', allTestValues['StringList']!),
+ preferences.setValue(
+ 'String', 'flutter.String', allTestValues['flutter.String']!),
+ preferences.setValue(
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!),
+ preferences.setValue(
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!),
+ preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!),
+ preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!)
+ ]);
+ await preferences.clearWithPrefix('');
+ final Map<String, Object> values = await preferences.getAllWithPrefix('');
+ expect(values['String'], null);
+ expect(values['Bool'], null);
+ expect(values['Int'], null);
+ expect(values['Double'], null);
+ expect(values['StringList'], null);
+ expect(values['flutter.String'], null);
+ expect(values['flutter.Bool'], null);
+ expect(values['flutter.Int'], null);
+ expect(values['flutter.Double'], null);
+ expect(values['flutter.StringList'], null);
+ });
+
+ testWidgets('getAll', (WidgetTester _) async {
+ await Future.wait(<Future<bool>>[
+ preferences.setValue(
+ 'String', 'flutter.String', allTestValues['flutter.String']!),
+ preferences.setValue(
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!),
+ preferences.setValue(
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!),
+ preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!),
+ preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!)
+ ]);
final Map<String, Object> values = await preferences.getAll();
+ expect(values['flutter.String'], allTestValues['flutter.String']);
+ expect(values['flutter.Bool'], allTestValues['flutter.Bool']);
+ expect(values['flutter.Int'], allTestValues['flutter.Int']);
+ expect(values['flutter.Double'], allTestValues['flutter.Double']);
+ expect(values['flutter.StringList'], allTestValues['flutter.StringList']);
+ });
+
+ testWidgets('remove', (WidgetTester _) async {
+ const String key = 'testKey';
+ await preferences.setValue(
+ 'String', key, allTestValues['flutter.String']!);
+ await preferences.setValue('Bool', key, allTestValues['flutter.Bool']!);
+ await preferences.setValue('Int', key, allTestValues['flutter.Int']!);
+ await preferences.setValue(
+ 'Double', key, allTestValues['flutter.Double']!);
+ await preferences.setValue(
+ 'StringList', key, allTestValues['flutter.StringList']!);
+ await preferences.remove(key);
+ final Map<String, Object> values = await preferences.getAllWithPrefix('');
expect(values[key], isNull);
});
- testWidgets('clearing', (WidgetTester _) async {
+ testWidgets('clear', (WidgetTester _) async {
await preferences.setValue(
- 'String', 'String', kTestValues['flutter.String']!);
- await preferences.setValue('Bool', 'bool', kTestValues['flutter.bool']!);
- await preferences.setValue('Int', 'int', kTestValues['flutter.int']!);
+ 'String', 'flutter.String', allTestValues['flutter.String']!);
await preferences.setValue(
- 'Double', 'double', kTestValues['flutter.double']!);
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!);
await preferences.setValue(
- 'StringList', 'List', kTestValues['flutter.List']!);
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!);
+ await preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!);
+ await preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!);
await preferences.clear();
final Map<String, Object> values = await preferences.getAll();
- expect(values['String'], null);
- expect(values['bool'], null);
- expect(values['int'], null);
- expect(values['double'], null);
- expect(values['List'], null);
+ expect(values['flutter.String'], null);
+ expect(values['flutter.Bool'], null);
+ expect(values['flutter.Int'], null);
+ expect(values['flutter.Double'], null);
+ expect(values['flutter.StringList'], null);
});
testWidgets('simultaneous writes', (WidgetTester _) async {
final List<Future<bool>> writes = <Future<bool>>[];
const int writeCount = 100;
for (int i = 1; i <= writeCount; i++) {
- writes.add(preferences.setValue('Int', prefixedKey('int'), i));
+ writes.add(preferences.setValue('Int', 'Int', i));
}
final List<bool> result = await Future.wait(writes, eagerError: true);
// All writes should succeed.
expect(result.where((bool element) => !element), isEmpty);
// The last write should win.
- final Map<String, Object> values = await preferences.getAll();
- expect(values[prefixedKey('int')], writeCount);
+ final Map<String, Object> values = await preferences.getAllWithPrefix('');
+ expect(values['Int'], writeCount);
});
testWidgets('string clash with lists, big integers and doubles',
(WidgetTester _) async {
- final String key = prefixedKey('akey');
+ const String key = 'akey';
const String value = 'a string value';
- await preferences.clear();
+ await preferences.clearWithPrefix('');
// Special prefixes used to store datatypes that can't be stored directly
// in SharedPreferences as strings instead.
@@ -137,7 +283,8 @@
for (final String prefix in specialPrefixes) {
expect(preferences.setValue('String', key, prefix + value),
throwsA(isA<PlatformException>()));
- final Map<String, Object> values = await preferences.getAll();
+ final Map<String, Object> values =
+ await preferences.getAllWithPrefix('');
expect(values[key], null);
}
});
diff --git a/packages/shared_preferences/shared_preferences_android/lib/shared_preferences_android.dart b/packages/shared_preferences/shared_preferences_android/lib/shared_preferences_android.dart
index da5147d..b77db6c 100644
--- a/packages/shared_preferences/shared_preferences_android/lib/shared_preferences_android.dart
+++ b/packages/shared_preferences/shared_preferences_android/lib/shared_preferences_android.dart
@@ -19,6 +19,8 @@
SharedPreferencesStorePlatform.instance = SharedPreferencesAndroid();
}
+ static const String _defaultPrefix = 'flutter.';
+
@override
Future<bool> remove(String key) async {
return (await _kChannel.invokeMethod<bool>(
@@ -37,17 +39,28 @@
@override
Future<bool> clear() async {
- return (await _kChannel.invokeMethod<bool>('clear'))!;
+ return clearWithPrefix(_defaultPrefix);
+ }
+
+ @override
+ Future<bool> clearWithPrefix(String prefix) async {
+ return (await _kChannel.invokeMethod<bool>(
+ 'clearWithPrefix',
+ <String, dynamic>{'prefix': prefix},
+ ))!;
}
@override
Future<Map<String, Object>> getAll() async {
- final Map<String, Object>? preferences =
- await _kChannel.invokeMapMethod<String, Object>('getAll');
+ return getAllWithPrefix(_defaultPrefix);
+ }
- if (preferences == null) {
- return <String, Object>{};
- }
- return preferences;
+ @override
+ Future<Map<String, Object>> getAllWithPrefix(String prefix) async {
+ return (await _kChannel.invokeMapMethod<String, Object>(
+ 'getAllWithPrefix',
+ <String, dynamic>{'prefix': prefix},
+ )) ??
+ <String, Object>{};
}
}
diff --git a/packages/shared_preferences/shared_preferences_android/pubspec.yaml b/packages/shared_preferences/shared_preferences_android/pubspec.yaml
index 963ed8b..02dd43a 100644
--- a/packages/shared_preferences/shared_preferences_android/pubspec.yaml
+++ b/packages/shared_preferences/shared_preferences_android/pubspec.yaml
@@ -2,7 +2,7 @@
description: Android implementation of the shared_preferences plugin
repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22
-version: 2.0.17
+version: 2.1.0
environment:
sdk: ">=2.17.0 <4.0.0"
@@ -20,7 +20,7 @@
dependencies:
flutter:
sdk: flutter
- shared_preferences_platform_interface: ^2.0.0
+ shared_preferences_platform_interface: ^2.2.0
dev_dependencies:
flutter_test:
diff --git a/packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart b/packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart
index f1043da..9c4ce21 100644
--- a/packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart
+++ b/packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart
@@ -16,15 +16,36 @@
'plugins.flutter.io/shared_preferences_android',
);
- const Map<String, Object> kTestValues = <String, Object>{
+ const Map<String, Object> flutterTestValues = <String, Object>{
'flutter.String': 'hello world',
'flutter.Bool': true,
'flutter.Int': 42,
'flutter.Double': 3.14159,
'flutter.StringList': <String>['foo', 'bar'],
};
- // Create a dummy in-memory implementation to back the mocked method channel
- // API to simplify validation of the expected calls.
+
+ const Map<String, Object> prefixTestValues = <String, Object>{
+ 'prefix.String': 'hello world',
+ 'prefix.Bool': true,
+ 'prefix.Int': 42,
+ 'prefix.Double': 3.14159,
+ 'prefix.StringList': <String>['foo', 'bar'],
+ };
+
+ const Map<String, Object> nonPrefixTestValues = <String, Object>{
+ 'String': 'hello world',
+ 'Bool': true,
+ 'Int': 42,
+ 'Double': 3.14159,
+ 'StringList': <String>['foo', 'bar'],
+ };
+
+ final Map<String, Object> allTestValues = <String, Object>{};
+
+ allTestValues.addAll(flutterTestValues);
+ allTestValues.addAll(prefixTestValues);
+ allTestValues.addAll(nonPrefixTestValues);
+
late InMemorySharedPreferencesStore testData;
final List<MethodCall> log = <MethodCall>[];
@@ -45,6 +66,12 @@
if (methodCall.method == 'getAll') {
return testData.getAll();
}
+ if (methodCall.method == 'getAllWithPrefix') {
+ final Map<String, Object?> arguments =
+ getArgumentDictionary(methodCall);
+ final String prefix = arguments['prefix']! as String;
+ return testData.getAllWithPrefix(prefix);
+ }
if (methodCall.method == 'remove') {
final Map<String, Object?> arguments =
getArgumentDictionary(methodCall);
@@ -54,6 +81,12 @@
if (methodCall.method == 'clear') {
return testData.clear();
}
+ if (methodCall.method == 'clearWithPrefix') {
+ final Map<String, Object?> arguments =
+ getArgumentDictionary(methodCall);
+ final String prefix = arguments['prefix']! as String;
+ return testData.clearWithPrefix(prefix);
+ }
final RegExp setterRegExp = RegExp(r'set(.*)');
final Match? match = setterRegExp.matchAsPrefix(methodCall.method);
if (match?.groupCount == 1) {
@@ -77,14 +110,21 @@
test('getAll', () async {
store = SharedPreferencesAndroid();
- testData = InMemorySharedPreferencesStore.withData(kTestValues);
- expect(await store.getAll(), kTestValues);
- expect(log.single.method, 'getAll');
+ testData = InMemorySharedPreferencesStore.withData(allTestValues);
+ expect(await store.getAll(), flutterTestValues);
+ expect(log.single.method, 'getAllWithPrefix');
+ });
+
+ test('getAllWithPrefix', () async {
+ store = SharedPreferencesAndroid();
+ testData = InMemorySharedPreferencesStore.withData(allTestValues);
+ expect(await store.getAllWithPrefix('prefix.'), prefixTestValues);
+ expect(log.single.method, 'getAllWithPrefix');
});
test('remove', () async {
store = SharedPreferencesAndroid();
- testData = InMemorySharedPreferencesStore.withData(kTestValues);
+ testData = InMemorySharedPreferencesStore.withData(allTestValues);
expect(await store.remove('flutter.String'), true);
expect(await store.remove('flutter.Bool'), true);
expect(await store.remove('flutter.Int'), true);
@@ -102,13 +142,13 @@
test('setValue', () async {
store = SharedPreferencesAndroid();
expect(await testData.getAll(), isEmpty);
- for (final String key in kTestValues.keys) {
- final Object value = kTestValues[key]!;
+ for (final String key in allTestValues.keys) {
+ final Object value = allTestValues[key]!;
expect(await store.setValue(key.split('.').last, key, value), true);
}
- expect(await testData.getAll(), kTestValues);
+ expect(await testData.getAll(), flutterTestValues);
- expect(log, hasLength(5));
+ expect(log, hasLength(15));
expect(log[0].method, 'setString');
expect(log[1].method, 'setBool');
expect(log[2].method, 'setInt');
@@ -118,11 +158,36 @@
test('clear', () async {
store = SharedPreferencesAndroid();
- testData = InMemorySharedPreferencesStore.withData(kTestValues);
+ testData = InMemorySharedPreferencesStore.withData(allTestValues);
expect(await testData.getAll(), isNotEmpty);
expect(await store.clear(), true);
expect(await testData.getAll(), isEmpty);
- expect(log.single.method, 'clear');
+ expect(log.single.method, 'clearWithPrefix');
+ });
+
+ test('clearWithPrefix', () async {
+ store = SharedPreferencesAndroid();
+ testData = InMemorySharedPreferencesStore.withData(allTestValues);
+
+ expect(await testData.getAllWithPrefix('prefix.'), isNotEmpty);
+ expect(await store.clearWithPrefix('prefix.'), true);
+ expect(await testData.getAllWithPrefix('prefix.'), isEmpty);
+ });
+
+ test('getAllWithNoPrefix', () async {
+ store = SharedPreferencesAndroid();
+ testData = InMemorySharedPreferencesStore.withData(allTestValues);
+
+ expect(await testData.getAllWithPrefix(''), hasLength(15));
+ });
+
+ test('clearWithNoPrefix', () async {
+ store = SharedPreferencesAndroid();
+ testData = InMemorySharedPreferencesStore.withData(allTestValues);
+
+ expect(await testData.getAllWithPrefix(''), isNotEmpty);
+ expect(await store.clearWithPrefix(''), true);
+ expect(await testData.getAllWithPrefix(''), isEmpty);
});
});
}
diff --git a/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md b/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md
index 06d7f95..d5a6fa2 100644
--- a/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md
+++ b/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.2.0
+
+* Adds `getAllWithPrefix` and `clearWithPrefix` methods.
+
## 2.1.5
* Clarifies explanation of endorsement in README.
diff --git a/packages/shared_preferences/shared_preferences_foundation/darwin/Classes/SharedPreferencesPlugin.swift b/packages/shared_preferences/shared_preferences_foundation/darwin/Classes/SharedPreferencesPlugin.swift
index c97698c..b334f68 100644
--- a/packages/shared_preferences/shared_preferences_foundation/darwin/Classes/SharedPreferencesPlugin.swift
+++ b/packages/shared_preferences/shared_preferences_foundation/darwin/Classes/SharedPreferencesPlugin.swift
@@ -22,8 +22,8 @@
UserDefaultsApiSetup.setUp(binaryMessenger: messenger, api: instance)
}
- func getAll() -> [String? : Any?] {
- return getAllPrefs();
+ func getAllWithPrefix(prefix: String) -> [String? : Any?] {
+ return getAllPrefs(prefix: prefix)
}
func setBool(key: String, value: Bool) {
@@ -42,23 +42,23 @@
UserDefaults.standard.removeObject(forKey: key)
}
- func clear() {
+ func clearWithPrefix(prefix: String) {
let defaults = UserDefaults.standard
- for (key, _) in getAllPrefs() {
+ for (key, _) in getAllPrefs(prefix: prefix) {
defaults.removeObject(forKey: key)
}
}
-}
-/// Returns all preferences stored by this plugin.
-private func getAllPrefs() -> [String: Any] {
- var filteredPrefs: [String: Any] = [:]
- if let appDomain = Bundle.main.bundleIdentifier,
- let prefs = UserDefaults.standard.persistentDomain(forName: appDomain)
- {
- for (key, value) in prefs where key.hasPrefix("flutter.") {
- filteredPrefs[key] = value
+ /// Returns all preferences stored with specified prefix.
+ func getAllPrefs(prefix: String) -> [String: Any] {
+ var filteredPrefs: [String: Any] = [:]
+ if let appDomain = Bundle.main.bundleIdentifier,
+ let prefs = UserDefaults.standard.persistentDomain(forName: appDomain)
+ {
+ for (key, value) in prefs where key.hasPrefix(prefix) {
+ filteredPrefs[key] = value
+ }
}
+ return filteredPrefs
}
- return filteredPrefs
}
diff --git a/packages/shared_preferences/shared_preferences_foundation/darwin/Classes/messages.g.swift b/packages/shared_preferences/shared_preferences_foundation/darwin/Classes/messages.g.swift
index 933217b..57a08e8 100644
--- a/packages/shared_preferences/shared_preferences_foundation/darwin/Classes/messages.g.swift
+++ b/packages/shared_preferences/shared_preferences_foundation/darwin/Classes/messages.g.swift
@@ -1,7 +1,7 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// Autogenerated from Pigeon (v5.0.0), do not edit directly.
+// Autogenerated from Pigeon (v9.1.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
import Foundation
@@ -14,15 +14,33 @@
#endif
-/// Generated class from Pigeon.
+
+private func wrapResult(_ result: Any?) -> [Any?] {
+ return [result]
+}
+
+private func wrapError(_ error: Any) -> [Any?] {
+ if let flutterError = error as? FlutterError {
+ return [
+ flutterError.code,
+ flutterError.message,
+ flutterError.details
+ ]
+ }
+ return [
+ "\(error)",
+ "\(type(of: error))",
+ "Stacktrace: \(Thread.callStackSymbols)"
+ ]
+}
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol UserDefaultsApi {
- func remove(key: String)
- func setBool(key: String, value: Bool)
- func setDouble(key: String, value: Double)
- func setValue(key: String, value: Any)
- func getAll() -> [String?: Any?]
- func clear()
+ func remove(key: String) throws
+ func setBool(key: String, value: Bool) throws
+ func setDouble(key: String, value: Double) throws
+ func setValue(key: String, value: Any) throws
+ func getAllWithPrefix(prefix: String) throws -> [String?: Any?]
+ func clearWithPrefix(prefix: String) throws
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
@@ -33,10 +51,14 @@
let removeChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.remove", binaryMessenger: binaryMessenger)
if let api = api {
removeChannel.setMessageHandler { message, reply in
- let args = message as! [Any?]
+ let args = message as! [Any]
let keyArg = args[0] as! String
- api.remove(key: keyArg)
- reply(wrapResult(nil))
+ do {
+ try api.remove(key: keyArg)
+ reply(wrapResult(nil))
+ } catch {
+ reply(wrapError(error))
+ }
}
} else {
removeChannel.setMessageHandler(nil)
@@ -44,11 +66,15 @@
let setBoolChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.setBool", binaryMessenger: binaryMessenger)
if let api = api {
setBoolChannel.setMessageHandler { message, reply in
- let args = message as! [Any?]
+ let args = message as! [Any]
let keyArg = args[0] as! String
let valueArg = args[1] as! Bool
- api.setBool(key: keyArg, value: valueArg)
- reply(wrapResult(nil))
+ do {
+ try api.setBool(key: keyArg, value: valueArg)
+ reply(wrapResult(nil))
+ } catch {
+ reply(wrapError(error))
+ }
}
} else {
setBoolChannel.setMessageHandler(nil)
@@ -56,11 +82,15 @@
let setDoubleChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.setDouble", binaryMessenger: binaryMessenger)
if let api = api {
setDoubleChannel.setMessageHandler { message, reply in
- let args = message as! [Any?]
+ let args = message as! [Any]
let keyArg = args[0] as! String
let valueArg = args[1] as! Double
- api.setDouble(key: keyArg, value: valueArg)
- reply(wrapResult(nil))
+ do {
+ try api.setDouble(key: keyArg, value: valueArg)
+ reply(wrapResult(nil))
+ } catch {
+ reply(wrapError(error))
+ }
}
} else {
setDoubleChannel.setMessageHandler(nil)
@@ -68,44 +98,48 @@
let setValueChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.setValue", binaryMessenger: binaryMessenger)
if let api = api {
setValueChannel.setMessageHandler { message, reply in
- let args = message as! [Any?]
+ let args = message as! [Any]
let keyArg = args[0] as! String
- let valueArg = args[1]!
- api.setValue(key: keyArg, value: valueArg)
- reply(wrapResult(nil))
+ let valueArg = args[1]
+ do {
+ try api.setValue(key: keyArg, value: valueArg)
+ reply(wrapResult(nil))
+ } catch {
+ reply(wrapError(error))
+ }
}
} else {
setValueChannel.setMessageHandler(nil)
}
- let getAllChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.getAll", binaryMessenger: binaryMessenger)
+ let getAllWithPrefixChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.getAllWithPrefix", binaryMessenger: binaryMessenger)
if let api = api {
- getAllChannel.setMessageHandler { _, reply in
- let result = api.getAll()
- reply(wrapResult(result))
+ getAllWithPrefixChannel.setMessageHandler { message, reply in
+ let args = message as! [Any]
+ let prefixArg = args[0] as! String
+ do {
+ let result = try api.getAllWithPrefix(prefix: prefixArg)
+ reply(wrapResult(result))
+ } catch {
+ reply(wrapError(error))
+ }
}
} else {
- getAllChannel.setMessageHandler(nil)
+ getAllWithPrefixChannel.setMessageHandler(nil)
}
- let clearChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.clear", binaryMessenger: binaryMessenger)
+ let clearWithPrefixChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.clearWithPrefix", binaryMessenger: binaryMessenger)
if let api = api {
- clearChannel.setMessageHandler { _, reply in
- api.clear()
- reply(wrapResult(nil))
+ clearWithPrefixChannel.setMessageHandler { message, reply in
+ let args = message as! [Any]
+ let prefixArg = args[0] as! String
+ do {
+ try api.clearWithPrefix(prefix: prefixArg)
+ reply(wrapResult(nil))
+ } catch {
+ reply(wrapError(error))
+ }
}
} else {
- clearChannel.setMessageHandler(nil)
+ clearWithPrefixChannel.setMessageHandler(nil)
}
}
}
-
-private func wrapResult(_ result: Any?) -> [Any?] {
- return [result]
-}
-
-private func wrapError(_ error: FlutterError) -> [Any?] {
- return [
- error.code,
- error.message,
- error.details
- ]
-}
diff --git a/packages/shared_preferences/shared_preferences_foundation/darwin/Tests/RunnerTests.swift b/packages/shared_preferences/shared_preferences_foundation/darwin/Tests/RunnerTests.swift
index a4dd4b5..c33b990 100644
--- a/packages/shared_preferences/shared_preferences_foundation/darwin/Tests/RunnerTests.swift
+++ b/packages/shared_preferences/shared_preferences_foundation/darwin/Tests/RunnerTests.swift
@@ -13,52 +13,61 @@
@testable import shared_preferences_foundation
class RunnerTests: XCTestCase {
+ let prefixes: [String] = ["aPrefix", ""]
+
func testSetAndGet() throws {
- let plugin = SharedPreferencesPlugin()
+ for aPrefix in prefixes {
+ let plugin = SharedPreferencesPlugin()
- plugin.setBool(key: "flutter.aBool", value: true)
- plugin.setDouble(key: "flutter.aDouble", value: 3.14)
- plugin.setValue(key: "flutter.anInt", value: 42)
- plugin.setValue(key: "flutter.aString", value: "hello world")
- plugin.setValue(key: "flutter.aStringList", value: ["hello", "world"])
+ plugin.setBool(key: "\(aPrefix)aBool", value: true)
+ plugin.setDouble(key: "\(aPrefix)aDouble", value: 3.14)
+ plugin.setValue(key: "\(aPrefix)anInt", value: 42)
+ plugin.setValue(key: "\(aPrefix)aString", value: "hello world")
+ plugin.setValue(key: "\(aPrefix)aStringList", value: ["hello", "world"])
- let storedValues = plugin.getAll()
- XCTAssertEqual(storedValues["flutter.aBool"] as? Bool, true)
- XCTAssertEqual(storedValues["flutter.aDouble"] as! Double, 3.14, accuracy: 0.0001)
- XCTAssertEqual(storedValues["flutter.anInt"] as? Int, 42)
- XCTAssertEqual(storedValues["flutter.aString"] as? String, "hello world")
- XCTAssertEqual(storedValues["flutter.aStringList"] as? Array<String>, ["hello", "world"])
+ let storedValues = plugin.getAllWithPrefix(prefix: aPrefix)
+ XCTAssertEqual(storedValues["\(aPrefix)aBool"] as? Bool, true)
+ XCTAssertEqual(storedValues["\(aPrefix)aDouble"] as! Double, 3.14, accuracy: 0.0001)
+ XCTAssertEqual(storedValues["\(aPrefix)anInt"] as? Int, 42)
+ XCTAssertEqual(storedValues["\(aPrefix)aString"] as? String, "hello world")
+ XCTAssertEqual(storedValues["\(aPrefix)aStringList"] as? Array<String>, ["hello", "world"])
+ }
}
func testRemove() throws {
- let plugin = SharedPreferencesPlugin()
- let testKey = "flutter.foo"
- plugin.setValue(key: testKey, value: 42)
+ for aPrefix in prefixes {
+ let plugin = SharedPreferencesPlugin()
+ let testKey = "\(aPrefix)foo"
+ plugin.setValue(key: testKey, value: 42)
- // Make sure there is something to remove, so the test can't pass due to a set failure.
- let preRemovalValues = plugin.getAll()
- XCTAssertEqual(preRemovalValues[testKey] as? Int, 42)
+ // Make sure there is something to remove, so the test can't pass due to a set failure.
+ let preRemovalValues = plugin.getAllWithPrefix(prefix: aPrefix)
+ XCTAssertEqual(preRemovalValues[testKey] as? Int, 42)
- // Then verify that removing it works.
- plugin.remove(key: testKey)
+ // Then verify that removing it works.
+ plugin.remove(key: testKey)
- let finalValues = plugin.getAll()
- XCTAssertNil(finalValues[testKey] as Any?)
+ let finalValues = plugin.getAllWithPrefix(prefix: aPrefix)
+ XCTAssertNil(finalValues[testKey] as Any?)
+ }
}
func testClear() throws {
- let plugin = SharedPreferencesPlugin()
- let testKey = "flutter.foo"
- plugin.setValue(key: testKey, value: 42)
+ for aPrefix in prefixes {
+ let plugin = SharedPreferencesPlugin()
+ let testKey = "\(aPrefix)foo"
+ plugin.setValue(key: testKey, value: 42)
- // Make sure there is something to clear, so the test can't pass due to a set failure.
- let preRemovalValues = plugin.getAll()
- XCTAssertEqual(preRemovalValues[testKey] as? Int, 42)
+ // Make sure there is something to clear, so the test can't pass due to a set failure.
+ let preRemovalValues = plugin.getAllWithPrefix(prefix: aPrefix)
+ XCTAssertEqual(preRemovalValues[testKey] as? Int, 42)
- // Then verify that clearing works.
- plugin.clear()
+ // Then verify that clearing works.
+ plugin.clearWithPrefix(prefix: aPrefix)
- let finalValues = plugin.getAll()
- XCTAssertNil(finalValues[testKey] as Any?)
+ let finalValues = plugin.getAllWithPrefix(prefix: aPrefix)
+ XCTAssertNil(finalValues[testKey] as Any?)
+ }
}
+
}
diff --git a/packages/shared_preferences/shared_preferences_foundation/example/integration_test/shared_preferences_test.dart b/packages/shared_preferences/shared_preferences_foundation/example/integration_test/shared_preferences_test.dart
index b3c1973..7a8fb4a 100644
--- a/packages/shared_preferences/shared_preferences_foundation/example/integration_test/shared_preferences_test.dart
+++ b/packages/shared_preferences/shared_preferences_foundation/example/integration_test/shared_preferences_test.dart
@@ -10,22 +10,36 @@
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('SharedPreferencesFoundation', () {
- const Map<String, Object> kTestValues = <String, Object>{
+ const Map<String, Object> flutterTestValues = <String, Object>{
'flutter.String': 'hello world',
- 'flutter.bool': true,
- 'flutter.int': 42,
- 'flutter.double': 3.14159,
- 'flutter.List': <String>['foo', 'bar'],
+ 'flutter.Bool': true,
+ 'flutter.Int': 42,
+ 'flutter.Double': 3.14159,
+ 'flutter.StringList': <String>['foo', 'bar'],
};
- const Map<String, Object> kTestValues2 = <String, Object>{
- 'flutter.String': 'goodbye world',
- 'flutter.bool': false,
- 'flutter.int': 1337,
- 'flutter.double': 2.71828,
- 'flutter.List': <String>['baz', 'quox'],
+ const Map<String, Object> prefixTestValues = <String, Object>{
+ 'prefix.String': 'hello world',
+ 'prefix.Bool': true,
+ 'prefix.Int': 42,
+ 'prefix.Double': 3.14159,
+ 'prefix.StringList': <String>['foo', 'bar'],
};
+ const Map<String, Object> nonPrefixTestValues = <String, Object>{
+ 'String': 'hello world',
+ 'Bool': true,
+ 'Int': 42,
+ 'Double': 3.14159,
+ 'StringList': <String>['foo', 'bar'],
+ };
+
+ final Map<String, Object> allTestValues = <String, Object>{};
+
+ allTestValues.addAll(flutterTestValues);
+ allTestValues.addAll(prefixTestValues);
+ allTestValues.addAll(nonPrefixTestValues);
+
late SharedPreferencesStorePlatform preferences;
setUp(() async {
@@ -33,74 +47,220 @@
});
tearDown(() {
- preferences.clear();
+ preferences.clearWithPrefix('');
});
- // Normally the app-facing package adds the prefix, but since this test
- // bypasses the app-facing package it needs to be manually added.
- String prefixedKey(String key) {
- return 'flutter.$key';
- }
-
testWidgets('reading', (WidgetTester _) async {
- final Map<String, Object> values = await preferences.getAll();
- expect(values[prefixedKey('String')], isNull);
- expect(values[prefixedKey('bool')], isNull);
- expect(values[prefixedKey('int')], isNull);
- expect(values[prefixedKey('double')], isNull);
- expect(values[prefixedKey('List')], isNull);
+ final Map<String, Object> values = await preferences.getAllWithPrefix('');
+ expect(values['String'], isNull);
+ expect(values['Bool'], isNull);
+ expect(values['Int'], isNull);
+ expect(values['Double'], isNull);
+ expect(values['StringList'], isNull);
});
- testWidgets('writing', (WidgetTester _) async {
+ testWidgets('getAllWithPrefix', (WidgetTester _) async {
await Future.wait(<Future<bool>>[
preferences.setValue(
- 'String', prefixedKey('String'), kTestValues2['flutter.String']!),
+ 'String', 'prefix.String', allTestValues['prefix.String']!),
preferences.setValue(
- 'Bool', prefixedKey('bool'), kTestValues2['flutter.bool']!),
+ 'Bool', 'prefix.Bool', allTestValues['prefix.Bool']!),
+ preferences.setValue('Int', 'prefix.Int', allTestValues['prefix.Int']!),
preferences.setValue(
- 'Int', prefixedKey('int'), kTestValues2['flutter.int']!),
+ 'Double', 'prefix.Double', allTestValues['prefix.Double']!),
+ preferences.setValue('StringList', 'prefix.StringList',
+ allTestValues['prefix.StringList']!),
preferences.setValue(
- 'Double', prefixedKey('double'), kTestValues2['flutter.double']!),
+ 'String', 'flutter.String', allTestValues['flutter.String']!),
preferences.setValue(
- 'StringList', prefixedKey('List'), kTestValues2['flutter.List']!)
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!),
+ preferences.setValue(
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!),
+ preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!),
+ preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!)
]);
- final Map<String, Object> values = await preferences.getAll();
- expect(values[prefixedKey('String')], kTestValues2['flutter.String']);
- expect(values[prefixedKey('bool')], kTestValues2['flutter.bool']);
- expect(values[prefixedKey('int')], kTestValues2['flutter.int']);
- expect(values[prefixedKey('double')], kTestValues2['flutter.double']);
- expect(values[prefixedKey('List')], kTestValues2['flutter.List']);
+ final Map<String, Object> values =
+ await preferences.getAllWithPrefix('prefix.');
+ expect(values['prefix.String'], allTestValues['prefix.String']);
+ expect(values['prefix.Bool'], allTestValues['prefix.Bool']);
+ expect(values['prefix.Int'], allTestValues['prefix.Int']);
+ expect(values['prefix.Double'], allTestValues['prefix.Double']);
+ expect(values['prefix.StringList'], allTestValues['prefix.StringList']);
});
- testWidgets('removing', (WidgetTester _) async {
- final String key = prefixedKey('testKey');
- await preferences.setValue('String', key, kTestValues['flutter.String']!);
- await preferences.setValue('Bool', key, kTestValues['flutter.bool']!);
- await preferences.setValue('Int', key, kTestValues['flutter.int']!);
- await preferences.setValue('Double', key, kTestValues['flutter.double']!);
- await preferences.setValue(
- 'StringList', key, kTestValues['flutter.List']!);
- await preferences.remove(key);
+ testWidgets('clearWithPrefix', (WidgetTester _) async {
+ await Future.wait(<Future<bool>>[
+ preferences.setValue(
+ 'String', 'prefix.String', allTestValues['prefix.String']!),
+ preferences.setValue(
+ 'Bool', 'prefix.Bool', allTestValues['prefix.Bool']!),
+ preferences.setValue('Int', 'prefix.Int', allTestValues['prefix.Int']!),
+ preferences.setValue(
+ 'Double', 'prefix.Double', allTestValues['prefix.Double']!),
+ preferences.setValue('StringList', 'prefix.StringList',
+ allTestValues['prefix.StringList']!),
+ preferences.setValue(
+ 'String', 'flutter.String', allTestValues['flutter.String']!),
+ preferences.setValue(
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!),
+ preferences.setValue(
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!),
+ preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!),
+ preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!)
+ ]);
+ await preferences.clearWithPrefix('prefix.');
+ Map<String, Object> values =
+ await preferences.getAllWithPrefix('prefix.');
+ expect(values['prefix.String'], null);
+ expect(values['prefix.Bool'], null);
+ expect(values['prefix.Int'], null);
+ expect(values['prefix.Double'], null);
+ expect(values['prefix.StringList'], null);
+ values = await preferences.getAllWithPrefix('flutter.');
+ expect(values['flutter.String'], allTestValues['flutter.String']);
+ expect(values['flutter.Bool'], allTestValues['flutter.Bool']);
+ expect(values['flutter.Int'], allTestValues['flutter.Int']);
+ expect(values['flutter.Double'], allTestValues['flutter.Double']);
+ expect(values['flutter.StringList'], allTestValues['flutter.StringList']);
+ });
+
+ testWidgets('getAllWithNoPrefix', (WidgetTester _) async {
+ await Future.wait(<Future<bool>>[
+ preferences.setValue('String', 'String', allTestValues['String']!),
+ preferences.setValue('Bool', 'Bool', allTestValues['Bool']!),
+ preferences.setValue('Int', 'Int', allTestValues['Int']!),
+ preferences.setValue('Double', 'Double', allTestValues['Double']!),
+ preferences.setValue(
+ 'StringList', 'StringList', allTestValues['StringList']!),
+ preferences.setValue(
+ 'String', 'flutter.String', allTestValues['flutter.String']!),
+ preferences.setValue(
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!),
+ preferences.setValue(
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!),
+ preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!),
+ preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!)
+ ]);
+ final Map<String, Object> values = await preferences.getAllWithPrefix('');
+ expect(values['String'], allTestValues['String']);
+ expect(values['Bool'], allTestValues['Bool']);
+ expect(values['Int'], allTestValues['Int']);
+ expect(values['Double'], allTestValues['Double']);
+ expect(values['StringList'], allTestValues['StringList']);
+ expect(values['flutter.String'], allTestValues['flutter.String']);
+ expect(values['flutter.Bool'], allTestValues['flutter.Bool']);
+ expect(values['flutter.Int'], allTestValues['flutter.Int']);
+ expect(values['flutter.Double'], allTestValues['flutter.Double']);
+ expect(values['flutter.StringList'], allTestValues['flutter.StringList']);
+ });
+
+ testWidgets('clearWithNoPrefix', (WidgetTester _) async {
+ await Future.wait(<Future<bool>>[
+ preferences.setValue('String', 'String', allTestValues['String']!),
+ preferences.setValue('Bool', 'Bool', allTestValues['Bool']!),
+ preferences.setValue('Int', 'Int', allTestValues['Int']!),
+ preferences.setValue('Double', 'Double', allTestValues['Double']!),
+ preferences.setValue(
+ 'StringList', 'StringList', allTestValues['StringList']!),
+ preferences.setValue(
+ 'String', 'flutter.String', allTestValues['flutter.String']!),
+ preferences.setValue(
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!),
+ preferences.setValue(
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!),
+ preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!),
+ preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!)
+ ]);
+ await preferences.clearWithPrefix('');
+ final Map<String, Object> values = await preferences.getAllWithPrefix('');
+ expect(values['String'], null);
+ expect(values['Bool'], null);
+ expect(values['Int'], null);
+ expect(values['Double'], null);
+ expect(values['StringList'], null);
+ expect(values['flutter.String'], null);
+ expect(values['flutter.Bool'], null);
+ expect(values['flutter.Int'], null);
+ expect(values['flutter.Double'], null);
+ expect(values['flutter.StringList'], null);
+ });
+
+ testWidgets('getAll', (WidgetTester _) async {
+ await Future.wait(<Future<bool>>[
+ preferences.setValue(
+ 'String', 'flutter.String', allTestValues['flutter.String']!),
+ preferences.setValue(
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!),
+ preferences.setValue(
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!),
+ preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!),
+ preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!)
+ ]);
final Map<String, Object> values = await preferences.getAll();
+ expect(values['flutter.String'], allTestValues['flutter.String']);
+ expect(values['flutter.Bool'], allTestValues['flutter.Bool']);
+ expect(values['flutter.Int'], allTestValues['flutter.Int']);
+ expect(values['flutter.Double'], allTestValues['flutter.Double']);
+ expect(values['flutter.StringList'], allTestValues['flutter.StringList']);
+ });
+
+ testWidgets('remove', (WidgetTester _) async {
+ const String key = 'testKey';
+ await preferences.setValue(
+ 'String', key, allTestValues['flutter.String']!);
+ await preferences.setValue('Bool', key, allTestValues['flutter.Bool']!);
+ await preferences.setValue('Int', key, allTestValues['flutter.Int']!);
+ await preferences.setValue(
+ 'Double', key, allTestValues['flutter.Double']!);
+ await preferences.setValue(
+ 'StringList', key, allTestValues['flutter.StringList']!);
+ await preferences.remove(key);
+ final Map<String, Object> values = await preferences.getAllWithPrefix('');
expect(values[key], isNull);
});
- testWidgets('clearing', (WidgetTester _) async {
+ testWidgets('clear', (WidgetTester _) async {
await preferences.setValue(
- 'String', 'String', kTestValues['flutter.String']!);
- await preferences.setValue('Bool', 'bool', kTestValues['flutter.bool']!);
- await preferences.setValue('Int', 'int', kTestValues['flutter.int']!);
+ 'String', 'flutter.String', allTestValues['flutter.String']!);
await preferences.setValue(
- 'Double', 'double', kTestValues['flutter.double']!);
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!);
await preferences.setValue(
- 'StringList', 'List', kTestValues['flutter.List']!);
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!);
+ await preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!);
+ await preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!);
await preferences.clear();
final Map<String, Object> values = await preferences.getAll();
- expect(values['String'], null);
- expect(values['bool'], null);
- expect(values['int'], null);
- expect(values['double'], null);
- expect(values['List'], null);
+ expect(values['flutter.String'], null);
+ expect(values['flutter.Bool'], null);
+ expect(values['flutter.Int'], null);
+ expect(values['flutter.Double'], null);
+ expect(values['flutter.StringList'], null);
+ });
+
+ testWidgets('simultaneous writes', (WidgetTester _) async {
+ final List<Future<bool>> writes = <Future<bool>>[];
+ const int writeCount = 100;
+ for (int i = 1; i <= writeCount; i++) {
+ writes.add(preferences.setValue('Int', 'Int', i));
+ }
+ final List<bool> result = await Future.wait(writes, eagerError: true);
+ // All writes should succeed.
+ expect(result.where((bool element) => !element), isEmpty);
+ // The last write should win.
+ final Map<String, Object> values = await preferences.getAllWithPrefix('');
+ expect(values['Int'], writeCount);
});
});
}
diff --git a/packages/shared_preferences/shared_preferences_foundation/lib/messages.g.dart b/packages/shared_preferences/shared_preferences_foundation/lib/messages.g.dart
index f7c6c21..a74c34d 100644
--- a/packages/shared_preferences/shared_preferences_foundation/lib/messages.g.dart
+++ b/packages/shared_preferences/shared_preferences_foundation/lib/messages.g.dart
@@ -1,9 +1,10 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// Autogenerated from Pigeon (v5.0.0), do not edit directly.
+// Autogenerated from Pigeon (v9.1.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
+
import 'dart:async';
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
@@ -108,11 +109,12 @@
}
}
- Future<Map<String?, Object?>> getAll() async {
+ Future<Map<String?, Object?>> getAllWithPrefix(String arg_prefix) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
- 'dev.flutter.pigeon.UserDefaultsApi.getAll', codec,
+ 'dev.flutter.pigeon.UserDefaultsApi.getAllWithPrefix', codec,
binaryMessenger: _binaryMessenger);
- final List<Object?>? replyList = await channel.send(null) as List<Object?>?;
+ final List<Object?>? replyList =
+ await channel.send(<Object?>[arg_prefix]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
@@ -134,11 +136,12 @@
}
}
- Future<void> clear() async {
+ Future<void> clearWithPrefix(String arg_prefix) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
- 'dev.flutter.pigeon.UserDefaultsApi.clear', codec,
+ 'dev.flutter.pigeon.UserDefaultsApi.clearWithPrefix', codec,
binaryMessenger: _binaryMessenger);
- final List<Object?>? replyList = await channel.send(null) as List<Object?>?;
+ final List<Object?>? replyList =
+ await channel.send(<Object?>[arg_prefix]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
diff --git a/packages/shared_preferences/shared_preferences_foundation/lib/shared_preferences_foundation.dart b/packages/shared_preferences/shared_preferences_foundation/lib/shared_preferences_foundation.dart
index 46b0ec4..f535ab9 100644
--- a/packages/shared_preferences/shared_preferences_foundation/lib/shared_preferences_foundation.dart
+++ b/packages/shared_preferences/shared_preferences_foundation/lib/shared_preferences_foundation.dart
@@ -11,6 +11,9 @@
/// iOS and macOS implementation of shared_preferences.
class SharedPreferencesFoundation extends SharedPreferencesStorePlatform {
final UserDefaultsApi _api = UserDefaultsApi();
+
+ static const String _defautPrefix = 'flutter.';
+
late final Map<String, _Setter> _setters = <String, _Setter>{
'Bool': (String key, Object value) {
return _api.setBool(key, value as bool);
@@ -37,13 +40,23 @@
@override
Future<bool> clear() async {
- await _api.clear();
+ return clearWithPrefix(_defautPrefix);
+ }
+
+ @override
+ Future<bool> clearWithPrefix(String prefix) async {
+ await _api.clearWithPrefix(prefix);
return true;
}
@override
Future<Map<String, Object>> getAll() async {
- final Map<String?, Object?> result = await _api.getAll();
+ return getAllWithPrefix(_defautPrefix);
+ }
+
+ @override
+ Future<Map<String, Object>> getAllWithPrefix(String prefix) async {
+ final Map<String?, Object?> result = await _api.getAllWithPrefix(prefix);
return result.cast<String, Object>();
}
diff --git a/packages/shared_preferences/shared_preferences_foundation/pigeons/messages.dart b/packages/shared_preferences/shared_preferences_foundation/pigeons/messages.dart
index 81848ed..e651546 100644
--- a/packages/shared_preferences/shared_preferences_foundation/pigeons/messages.dart
+++ b/packages/shared_preferences/shared_preferences_foundation/pigeons/messages.dart
@@ -20,6 +20,6 @@
void setValue(String key, Object value);
// TODO(stuartmorgan): Make these non-nullable once
// https://github.com/flutter/flutter/issues/97848 is fixed.
- Map<String?, Object?> getAll();
- void clear();
+ Map<String?, Object?> getAllWithPrefix(String prefix);
+ void clearWithPrefix(String prefix);
}
diff --git a/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml b/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml
index 74d7cfc..b6b91cc 100644
--- a/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml
+++ b/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml
@@ -2,7 +2,7 @@
description: iOS and macOS implementation of the shared_preferences plugin.
repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_foundation
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22
-version: 2.1.5
+version: 2.2.0
environment:
sdk: ">=2.17.0 <4.0.0"
@@ -24,9 +24,9 @@
dependencies:
flutter:
sdk: flutter
- shared_preferences_platform_interface: ^2.0.0
+ shared_preferences_platform_interface: ^2.2.0
dev_dependencies:
flutter_test:
sdk: flutter
- pigeon: ^5.0.0
+ pigeon: ^9.0.0
diff --git a/packages/shared_preferences/shared_preferences_foundation/test/shared_preferences_foundation_test.dart b/packages/shared_preferences/shared_preferences_foundation/test/shared_preferences_foundation_test.dart
index 6c0635a..5859aad 100644
--- a/packages/shared_preferences/shared_preferences_foundation/test/shared_preferences_foundation_test.dart
+++ b/packages/shared_preferences/shared_preferences_foundation/test/shared_preferences_foundation_test.dart
@@ -13,8 +13,11 @@
final Map<String, Object> items = <String, Object>{};
@override
- Map<String?, Object?> getAll() {
- return items;
+ Map<String?, Object?> getAllWithPrefix(String prefix) {
+ return <String?, Object?>{
+ for (final String key in items.keys)
+ if (key.startsWith(prefix)) key: items[key]
+ };
}
@override
@@ -38,8 +41,12 @@
}
@override
- void clear() {
- items.clear();
+ void clearWithPrefix(String prefix) {
+ items.keys.toList().forEach((String key) {
+ if (key.startsWith(prefix)) {
+ items.remove(key);
+ }
+ });
}
}
@@ -47,6 +54,36 @@
TestWidgetsFlutterBinding.ensureInitialized();
late _MockSharedPreferencesApi api;
+ const Map<String, Object> flutterTestValues = <String, Object>{
+ 'flutter.String': 'hello world',
+ 'flutter.Bool': true,
+ 'flutter.Int': 42,
+ 'flutter.Double': 3.14159,
+ 'flutter.StringList': <String>['foo', 'bar'],
+ };
+
+ const Map<String, Object> prefixTestValues = <String, Object>{
+ 'prefix.String': 'hello world',
+ 'prefix.Bool': true,
+ 'prefix.Int': 42,
+ 'prefix.Double': 3.14159,
+ 'prefix.StringList': <String>['foo', 'bar'],
+ };
+
+ const Map<String, Object> nonPrefixTestValues = <String, Object>{
+ 'String': 'hello world',
+ 'Bool': true,
+ 'Int': 42,
+ 'Double': 3.14159,
+ 'StringList': <String>['foo', 'bar'],
+ };
+
+ final Map<String, Object> allTestValues = <String, Object>{};
+
+ allTestValues.addAll(flutterTestValues);
+ allTestValues.addAll(prefixTestValues);
+ allTestValues.addAll(nonPrefixTestValues);
+
setUp(() {
api = _MockSharedPreferencesApi();
TestUserDefaultsApi.setup(api);
@@ -72,21 +109,39 @@
expect(api.items.containsKey('flutter.hi'), isFalse);
});
+ test('clearWithPrefix', () async {
+ final SharedPreferencesFoundation plugin = SharedPreferencesFoundation();
+ for (final String key in allTestValues.keys) {
+ api.items[key] = allTestValues[key]!;
+ }
+
+ Map<String?, Object?> all = await plugin.getAllWithPrefix('prefix.');
+ expect(all.length, 5);
+ await plugin.clearWithPrefix('prefix.');
+ all = await plugin.getAll();
+ expect(all.length, 5);
+ all = await plugin.getAllWithPrefix('prefix.');
+ expect(all.length, 0);
+ });
+
test('getAll', () async {
final SharedPreferencesFoundation plugin = SharedPreferencesFoundation();
- api.items['flutter.aBool'] = true;
- api.items['flutter.aDouble'] = 3.14;
- api.items['flutter.anInt'] = 42;
- api.items['flutter.aString'] = 'hello world';
- api.items['flutter.aStringList'] = <String>['hello', 'world'];
+ for (final String key in flutterTestValues.keys) {
+ api.items[key] = flutterTestValues[key]!;
+ }
final Map<String?, Object?> all = await plugin.getAll();
expect(all.length, 5);
- expect(all['flutter.aBool'], api.items['flutter.aBool']);
- expect(all['flutter.aDouble'],
- closeTo(api.items['flutter.aDouble']! as num, 0.0001));
- expect(all['flutter.anInt'], api.items['flutter.anInt']);
- expect(all['flutter.aString'], api.items['flutter.aString']);
- expect(all['flutter.aStringList'], api.items['flutter.aStringList']);
+ expect(all, flutterTestValues);
+ });
+
+ test('getAllWithPrefix', () async {
+ final SharedPreferencesFoundation plugin = SharedPreferencesFoundation();
+ for (final String key in allTestValues.keys) {
+ api.items[key] = allTestValues[key]!;
+ }
+ final Map<String?, Object?> all = await plugin.getAllWithPrefix('prefix.');
+ expect(all.length, 5);
+ expect(all, prefixTestValues);
});
test('setValue', () async {
@@ -112,4 +167,27 @@
await plugin.setValue('Map', 'flutter.key', <String, String>{});
}, throwsA(isA<PlatformException>()));
});
+
+ test('getAllWithNoPrefix', () async {
+ final SharedPreferencesFoundation plugin = SharedPreferencesFoundation();
+ for (final String key in allTestValues.keys) {
+ api.items[key] = allTestValues[key]!;
+ }
+ final Map<String?, Object?> all = await plugin.getAllWithPrefix('');
+ expect(all.length, 15);
+ expect(all, allTestValues);
+ });
+
+ test('clearWithNoPrefix', () async {
+ final SharedPreferencesFoundation plugin = SharedPreferencesFoundation();
+ for (final String key in allTestValues.keys) {
+ api.items[key] = allTestValues[key]!;
+ }
+
+ Map<String?, Object?> all = await plugin.getAllWithPrefix('');
+ expect(all.length, 15);
+ await plugin.clearWithPrefix('');
+ all = await plugin.getAllWithPrefix('');
+ expect(all.length, 0);
+ });
}
diff --git a/packages/shared_preferences/shared_preferences_foundation/test/test_api.g.dart b/packages/shared_preferences/shared_preferences_foundation/test/test_api.g.dart
index 12f97bd..bbc0d2a 100644
--- a/packages/shared_preferences/shared_preferences_foundation/test/test_api.g.dart
+++ b/packages/shared_preferences/shared_preferences_foundation/test/test_api.g.dart
@@ -1,7 +1,7 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// Autogenerated from Pigeon (v5.0.0), do not edit directly.
+// Autogenerated from Pigeon (v9.1.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import
// ignore_for_file: avoid_relative_lib_imports
@@ -24,9 +24,9 @@
void setValue(String key, Object value);
- Map<String?, Object?> getAll();
+ Map<String?, Object?> getAllWithPrefix(String prefix);
- void clear();
+ void clearWithPrefix(String prefix);
static void setup(TestUserDefaultsApi? api,
{BinaryMessenger? binaryMessenger}) {
@@ -117,28 +117,39 @@
}
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
- 'dev.flutter.pigeon.UserDefaultsApi.getAll', codec,
+ 'dev.flutter.pigeon.UserDefaultsApi.getAllWithPrefix', codec,
binaryMessenger: binaryMessenger);
if (api == null) {
channel.setMockMessageHandler(null);
} else {
channel.setMockMessageHandler((Object? message) async {
- // ignore message
- final Map<String?, Object?> output = api.getAll();
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.UserDefaultsApi.getAllWithPrefix was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final String? arg_prefix = (args[0] as String?);
+ assert(arg_prefix != null,
+ 'Argument for dev.flutter.pigeon.UserDefaultsApi.getAllWithPrefix was null, expected non-null String.');
+ final Map<String?, Object?> output =
+ api.getAllWithPrefix(arg_prefix!);
return <Object?>[output];
});
}
}
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
- 'dev.flutter.pigeon.UserDefaultsApi.clear', codec,
+ 'dev.flutter.pigeon.UserDefaultsApi.clearWithPrefix', codec,
binaryMessenger: binaryMessenger);
if (api == null) {
channel.setMockMessageHandler(null);
} else {
channel.setMockMessageHandler((Object? message) async {
- // ignore message
- api.clear();
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.UserDefaultsApi.clearWithPrefix was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final String? arg_prefix = (args[0] as String?);
+ assert(arg_prefix != null,
+ 'Argument for dev.flutter.pigeon.UserDefaultsApi.clearWithPrefix was null, expected non-null String.');
+ api.clearWithPrefix(arg_prefix!);
return <Object?>[];
});
}
diff --git a/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md
index d3736fa..4ba0cec 100644
--- a/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md
+++ b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.2.0
+
+* Adds `getAllWithPrefix` and `clearWithPrefix` methods.
+
## 2.1.5
* Clarifies explanation of endorsement in README.
diff --git a/packages/shared_preferences/shared_preferences_linux/example/integration_test/shared_preferences_test.dart b/packages/shared_preferences/shared_preferences_linux/example/integration_test/shared_preferences_test.dart
index 664048a..8939d0a 100644
--- a/packages/shared_preferences/shared_preferences_linux/example/integration_test/shared_preferences_test.dart
+++ b/packages/shared_preferences/shared_preferences_linux/example/integration_test/shared_preferences_test.dart
@@ -38,34 +38,36 @@
testWidgets('reading', (WidgetTester _) async {
final Map<String, Object> all = await preferences.getAll();
- expect(all['String'], isNull);
- expect(all['bool'], isNull);
- expect(all['int'], isNull);
- expect(all['double'], isNull);
- expect(all['List'], isNull);
+ expect(all['flutter.String'], isNull);
+ expect(all['flutter.bool'], isNull);
+ expect(all['flutter.int'], isNull);
+ expect(all['flutter.double'], isNull);
+ expect(all['flutter.List'], isNull);
});
testWidgets('writing', (WidgetTester _) async {
await Future.wait(<Future<bool>>[
preferences.setValue(
- 'String', 'String', kTestValues2['flutter.String']!),
- preferences.setValue('Bool', 'bool', kTestValues2['flutter.bool']!),
- preferences.setValue('Int', 'int', kTestValues2['flutter.int']!),
+ 'String', 'flutter.String', kTestValues2['flutter.String']!),
preferences.setValue(
- 'Double', 'double', kTestValues2['flutter.double']!),
+ 'Bool', 'flutter.bool', kTestValues2['flutter.bool']!),
preferences.setValue(
- 'StringList', 'List', kTestValues2['flutter.List']!)
+ 'Int', 'flutter.int', kTestValues2['flutter.int']!),
+ preferences.setValue(
+ 'Double', 'flutter.double', kTestValues2['flutter.double']!),
+ preferences.setValue(
+ 'StringList', 'flutter.List', kTestValues2['flutter.List']!)
]);
final Map<String, Object> all = await preferences.getAll();
- expect(all['String'], kTestValues2['flutter.String']);
- expect(all['bool'], kTestValues2['flutter.bool']);
- expect(all['int'], kTestValues2['flutter.int']);
- expect(all['double'], kTestValues2['flutter.double']);
- expect(all['List'], kTestValues2['flutter.List']);
+ expect(all['flutter.String'], kTestValues2['flutter.String']);
+ expect(all['flutter.bool'], kTestValues2['flutter.bool']);
+ expect(all['flutter.int'], kTestValues2['flutter.int']);
+ expect(all['flutter.double'], kTestValues2['flutter.double']);
+ expect(all['flutter.List'], kTestValues2['flutter.List']);
});
testWidgets('removing', (WidgetTester _) async {
- const String key = 'testKey';
+ const String key = 'flutter.testKey';
await Future.wait(<Future<bool>>[
preferences.setValue('String', key, kTestValues['flutter.String']!),
@@ -76,26 +78,28 @@
]);
await preferences.remove(key);
final Map<String, Object> all = await preferences.getAll();
- expect(all['testKey'], isNull);
+ expect(all[key], isNull);
});
testWidgets('clearing', (WidgetTester _) async {
await Future.wait(<Future<bool>>[
preferences.setValue(
- 'String', 'String', kTestValues['flutter.String']!),
- preferences.setValue('Bool', 'bool', kTestValues['flutter.bool']!),
- preferences.setValue('Int', 'int', kTestValues['flutter.int']!),
+ 'String', 'flutter.String', kTestValues['flutter.String']!),
preferences.setValue(
- 'Double', 'double', kTestValues['flutter.double']!),
- preferences.setValue('StringList', 'List', kTestValues['flutter.List']!)
+ 'Bool', 'flutter.bool', kTestValues['flutter.bool']!),
+ preferences.setValue('Int', 'flutter.int', kTestValues['flutter.int']!),
+ preferences.setValue(
+ 'Double', 'flutter.double', kTestValues['flutter.double']!),
+ preferences.setValue(
+ 'StringList', 'flutter.List', kTestValues['flutter.List']!)
]);
await preferences.clear();
final Map<String, Object> all = await preferences.getAll();
- expect(all['String'], null);
- expect(all['bool'], null);
- expect(all['int'], null);
- expect(all['double'], null);
- expect(all['List'], null);
+ expect(all['flutter.String'], null);
+ expect(all['flutter.bool'], null);
+ expect(all['flutter.int'], null);
+ expect(all['flutter.double'], null);
+ expect(all['flutter.List'], null);
});
});
}
diff --git a/packages/shared_preferences/shared_preferences_linux/lib/shared_preferences_linux.dart b/packages/shared_preferences/shared_preferences_linux/lib/shared_preferences_linux.dart
index 1cc1c41..738ccdc 100644
--- a/packages/shared_preferences/shared_preferences_linux/lib/shared_preferences_linux.dart
+++ b/packages/shared_preferences/shared_preferences_linux/lib/shared_preferences_linux.dart
@@ -21,6 +21,8 @@
@Deprecated('Use `SharedPreferencesStorePlatform.instance` instead.')
static SharedPreferencesLinux instance = SharedPreferencesLinux();
+ static const String _defaultPrefix = 'flutter.';
+
/// Registers the Linux implementation.
static void registerWith() {
SharedPreferencesStorePlatform.instance = SharedPreferencesLinux();
@@ -46,13 +48,8 @@
return fs.file(path.join(directory, 'shared_preferences.json'));
}
- /// Gets the preferences from the stored file. Once read, the preferences are
- /// maintained in memory.
- Future<Map<String, Object>> _readPreferences() async {
- if (_cachedPreferences != null) {
- return _cachedPreferences!;
- }
-
+ /// Gets the preferences from the stored file and saves them in cache.
+ Future<Map<String, Object>> _reload() async {
Map<String, Object> preferences = <String, Object>{};
final File? localDataFile = await _getLocalDataFile();
if (localDataFile != null && localDataFile.existsSync()) {
@@ -68,6 +65,12 @@
return preferences;
}
+ /// Checks for cached preferences and returns them or loads preferences from
+ /// file and returns and caches them.
+ Future<Map<String, Object>> _readPreferences() async {
+ return _cachedPreferences ?? await _reload();
+ }
+
/// Writes the cached preferences to disk. Returns [true] if the operation
/// succeeded.
Future<bool> _writePreferences(Map<String, Object> preferences) async {
@@ -91,14 +94,27 @@
@override
Future<bool> clear() async {
+ return clearWithPrefix(_defaultPrefix);
+ }
+
+ @override
+ Future<bool> clearWithPrefix(String prefix) async {
final Map<String, Object> preferences = await _readPreferences();
- preferences.clear();
+ preferences.removeWhere((String key, _) => key.startsWith(prefix));
return _writePreferences(preferences);
}
@override
Future<Map<String, Object>> getAll() async {
- return _readPreferences();
+ return getAllWithPrefix(_defaultPrefix);
+ }
+
+ @override
+ Future<Map<String, Object>> getAllWithPrefix(String prefix) async {
+ final Map<String, Object> withPrefix =
+ Map<String, Object>.from(await _readPreferences());
+ withPrefix.removeWhere((String key, _) => !key.startsWith(prefix));
+ return withPrefix;
}
@override
diff --git a/packages/shared_preferences/shared_preferences_linux/pubspec.yaml b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml
index 08fc024..ff3f6d2 100644
--- a/packages/shared_preferences/shared_preferences_linux/pubspec.yaml
+++ b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml
@@ -2,7 +2,7 @@
description: Linux implementation of the shared_preferences plugin
repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_linux
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22
-version: 2.1.5
+version: 2.2.0
environment:
sdk: ">=2.17.0 <4.0.0"
@@ -22,7 +22,7 @@
path: ^1.8.0
path_provider_linux: ^2.0.0
path_provider_platform_interface: ^2.0.0
- shared_preferences_platform_interface: ^2.0.0
+ shared_preferences_platform_interface: ^2.2.0
dev_dependencies:
flutter_test:
diff --git a/packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_test.dart b/packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_test.dart
index 176d1d9..a4f2854 100644
--- a/packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_test.dart
+++ b/packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_test.dart
@@ -1,6 +1,8 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:convert';
+
import 'package:file/memory.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart' as path;
@@ -15,6 +17,36 @@
SharedPreferencesLinux.registerWith();
+ const Map<String, Object> flutterTestValues = <String, Object>{
+ 'flutter.String': 'hello world',
+ 'flutter.Bool': true,
+ 'flutter.Int': 42,
+ 'flutter.Double': 3.14159,
+ 'flutter.StringList': <String>['foo', 'bar'],
+ };
+
+ const Map<String, Object> prefixTestValues = <String, Object>{
+ 'prefix.String': 'hello world',
+ 'prefix.Bool': true,
+ 'prefix.Int': 42,
+ 'prefix.Double': 3.14159,
+ 'prefix.StringList': <String>['foo', 'bar'],
+ };
+
+ const Map<String, Object> nonPrefixTestValues = <String, Object>{
+ 'String': 'hello world',
+ 'Bool': true,
+ 'Int': 42,
+ 'Double': 3.14159,
+ 'StringList': <String>['foo', 'bar'],
+ };
+
+ final Map<String, Object> allTestValues = <String, Object>{};
+
+ allTestValues.addAll(flutterTestValues);
+ allTestValues.addAll(prefixTestValues);
+ allTestValues.addAll(nonPrefixTestValues);
+
setUp(() {
fs = MemoryFileSystem.test();
pathProvider = FakePathProviderLinux();
@@ -49,13 +81,21 @@
});
test('getAll', () async {
- await writeTestFile('{"key1": "one", "key2": 2}');
+ await writeTestFile(json.encode(allTestValues));
final SharedPreferencesLinux prefs = getPreferences();
final Map<String, Object> values = await prefs.getAll();
- expect(values, hasLength(2));
- expect(values['key1'], 'one');
- expect(values['key2'], 2);
+ expect(values, hasLength(5));
+ expect(values, flutterTestValues);
+ });
+
+ test('getAllWithPrefix', () async {
+ await writeTestFile(json.encode(allTestValues));
+ final SharedPreferencesLinux prefs = getPreferences();
+
+ final Map<String, Object> values = await prefs.getAllWithPrefix('prefix.');
+ expect(values, hasLength(5));
+ expect(values, prefixTestValues);
});
test('remove', () async {
@@ -78,12 +118,43 @@
});
test('clear', () async {
- await writeTestFile('{"key1":"one","key2":2}');
+ await writeTestFile(json.encode(flutterTestValues));
final SharedPreferencesLinux prefs = getPreferences();
+ expect(await readTestFile(), json.encode(flutterTestValues));
await prefs.clear();
expect(await readTestFile(), '{}');
});
+
+ test('clearWithPrefix', () async {
+ await writeTestFile(json.encode(flutterTestValues));
+ final SharedPreferencesLinux prefs = getPreferences();
+ await prefs.clearWithPrefix('prefix.');
+ final Map<String, Object> noValues =
+ await prefs.getAllWithPrefix('prefix.');
+ expect(noValues, hasLength(0));
+
+ final Map<String, Object> values = await prefs.getAll();
+ expect(values, hasLength(5));
+ expect(values, flutterTestValues);
+ });
+
+ test('getAllWithNoPrefix', () async {
+ await writeTestFile(json.encode(allTestValues));
+ final SharedPreferencesLinux prefs = getPreferences();
+
+ final Map<String, Object> values = await prefs.getAllWithPrefix('');
+ expect(values, hasLength(15));
+ expect(values, allTestValues);
+ });
+
+ test('clearWithNoPrefix', () async {
+ await writeTestFile(json.encode(flutterTestValues));
+ final SharedPreferencesLinux prefs = getPreferences();
+ await prefs.clearWithPrefix('');
+ final Map<String, Object> noValues = await prefs.getAllWithPrefix('');
+ expect(noValues, hasLength(0));
+ });
}
/// Fake implementation of PathProviderLinux that returns hard-coded paths,
diff --git a/packages/shared_preferences/shared_preferences_web/CHANGELOG.md b/packages/shared_preferences/shared_preferences_web/CHANGELOG.md
index 1f1d2dc..b5efabc 100644
--- a/packages/shared_preferences/shared_preferences_web/CHANGELOG.md
+++ b/packages/shared_preferences/shared_preferences_web/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.1.0
+
+* Adds `getAllWithPrefix` and `clearWithPrefix` methods.
+
## 2.0.6
* Clarifies explanation of endorsement in README.
diff --git a/packages/shared_preferences/shared_preferences_web/example/integration_test/shared_preferences_web_test.dart b/packages/shared_preferences/shared_preferences_web/example/integration_test/shared_preferences_web_test.dart
index d3bfa49..ccc01df 100644
--- a/packages/shared_preferences/shared_preferences_web/example/integration_test/shared_preferences_web_test.dart
+++ b/packages/shared_preferences/shared_preferences_web/example/integration_test/shared_preferences_web_test.dart
@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:convert' show json;
import 'dart:html' as html;
import 'package:flutter_test/flutter_test.dart';
@@ -37,57 +36,257 @@
isA<SharedPreferencesPlugin>());
});
- testWidgets('getAll', (WidgetTester tester) async {
- final SharedPreferencesPlugin store = SharedPreferencesPlugin();
- expect(await store.getAll(), isEmpty);
+ const Map<String, Object> flutterTestValues = <String, Object>{
+ 'flutter.String': 'hello world',
+ 'flutter.Bool': true,
+ 'flutter.Int': 42,
+ 'flutter.Double': 3.14159,
+ 'flutter.StringList': <String>['foo', 'bar'],
+ };
- html.window.localStorage['flutter.testKey'] = '"test value"';
- html.window.localStorage['unprefixed_key'] = 'not a flutter value';
- final Map<String, Object> allData = await store.getAll();
- expect(allData, hasLength(1));
- expect(allData['flutter.testKey'], 'test value');
+ const Map<String, Object> prefixTestValues = <String, Object>{
+ 'prefix.String': 'hello world',
+ 'prefix.Bool': true,
+ 'prefix.Int': 42,
+ 'prefix.Double': 3.14159,
+ 'prefix.StringList': <String>['foo', 'bar'],
+ };
+
+ const Map<String, Object> nonPrefixTestValues = <String, Object>{
+ 'String': 'hello world',
+ 'Bool': true,
+ 'Int': 42,
+ 'Double': 3.14159,
+ 'StringList': <String>['foo', 'bar'],
+ };
+
+ final Map<String, Object> allTestValues = <String, Object>{};
+
+ allTestValues.addAll(flutterTestValues);
+ allTestValues.addAll(prefixTestValues);
+ allTestValues.addAll(nonPrefixTestValues);
+
+ late SharedPreferencesStorePlatform preferences;
+
+ setUp(() async {
+ preferences = SharedPreferencesStorePlatform.instance;
});
- testWidgets('remove', (WidgetTester tester) async {
- final SharedPreferencesPlugin store = SharedPreferencesPlugin();
- html.window.localStorage['flutter.testKey'] = '"test value"';
- expect(html.window.localStorage['flutter.testKey'], isNotNull);
- expect(await store.remove('flutter.testKey'), isTrue);
- expect(html.window.localStorage['flutter.testKey'], isNull);
- expect(
- () => store.remove('unprefixed'),
- throwsA(isA<FormatException>()),
- );
+ tearDown(() {
+ preferences.clearWithPrefix('');
});
- testWidgets('setValue', (WidgetTester tester) async {
- final SharedPreferencesPlugin store = SharedPreferencesPlugin();
- for (final String key in kTestValues.keys) {
- final dynamic value = kTestValues[key];
- expect(await store.setValue(key.split('.').last, key, value), true);
+ testWidgets('reading', (WidgetTester _) async {
+ final Map<String, Object> values = await preferences.getAllWithPrefix('');
+ expect(values['String'], isNull);
+ expect(values['Bool'], isNull);
+ expect(values['Int'], isNull);
+ expect(values['Double'], isNull);
+ expect(values['StringList'], isNull);
+ });
+
+ testWidgets('getAllWithPrefix', (WidgetTester _) async {
+ await Future.wait(<Future<bool>>[
+ preferences.setValue(
+ 'String', 'prefix.String', allTestValues['prefix.String']!),
+ preferences.setValue(
+ 'Bool', 'prefix.Bool', allTestValues['prefix.Bool']!),
+ preferences.setValue('Int', 'prefix.Int', allTestValues['prefix.Int']!),
+ preferences.setValue(
+ 'Double', 'prefix.Double', allTestValues['prefix.Double']!),
+ preferences.setValue('StringList', 'prefix.StringList',
+ allTestValues['prefix.StringList']!),
+ preferences.setValue(
+ 'String', 'flutter.String', allTestValues['flutter.String']!),
+ preferences.setValue(
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!),
+ preferences.setValue(
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!),
+ preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!),
+ preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!)
+ ]);
+ final Map<String, Object> values =
+ await preferences.getAllWithPrefix('prefix.');
+ expect(values['prefix.String'], allTestValues['prefix.String']);
+ expect(values['prefix.Bool'], allTestValues['prefix.Bool']);
+ expect(values['prefix.Int'], allTestValues['prefix.Int']);
+ expect(values['prefix.Double'], allTestValues['prefix.Double']);
+ expect(values['prefix.StringList'], allTestValues['prefix.StringList']);
+ });
+
+ testWidgets('clearWithPrefix', (WidgetTester _) async {
+ await Future.wait(<Future<bool>>[
+ preferences.setValue(
+ 'String', 'prefix.String', allTestValues['prefix.String']!),
+ preferences.setValue(
+ 'Bool', 'prefix.Bool', allTestValues['prefix.Bool']!),
+ preferences.setValue('Int', 'prefix.Int', allTestValues['prefix.Int']!),
+ preferences.setValue(
+ 'Double', 'prefix.Double', allTestValues['prefix.Double']!),
+ preferences.setValue('StringList', 'prefix.StringList',
+ allTestValues['prefix.StringList']!),
+ preferences.setValue(
+ 'String', 'flutter.String', allTestValues['flutter.String']!),
+ preferences.setValue(
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!),
+ preferences.setValue(
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!),
+ preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!),
+ preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!)
+ ]);
+ await preferences.clearWithPrefix('prefix.');
+ Map<String, Object> values =
+ await preferences.getAllWithPrefix('prefix.');
+ expect(values['prefix.String'], null);
+ expect(values['prefix.Bool'], null);
+ expect(values['prefix.Int'], null);
+ expect(values['prefix.Double'], null);
+ expect(values['prefix.StringList'], null);
+ values = await preferences.getAllWithPrefix('flutter.');
+ expect(values['flutter.String'], allTestValues['flutter.String']);
+ expect(values['flutter.Bool'], allTestValues['flutter.Bool']);
+ expect(values['flutter.Int'], allTestValues['flutter.Int']);
+ expect(values['flutter.Double'], allTestValues['flutter.Double']);
+ expect(values['flutter.StringList'], allTestValues['flutter.StringList']);
+ });
+
+ testWidgets('getAllWithNoPrefix', (WidgetTester _) async {
+ await Future.wait(<Future<bool>>[
+ preferences.setValue('String', 'String', allTestValues['String']!),
+ preferences.setValue('Bool', 'Bool', allTestValues['Bool']!),
+ preferences.setValue('Int', 'Int', allTestValues['Int']!),
+ preferences.setValue('Double', 'Double', allTestValues['Double']!),
+ preferences.setValue(
+ 'StringList', 'StringList', allTestValues['StringList']!),
+ preferences.setValue(
+ 'String', 'flutter.String', allTestValues['flutter.String']!),
+ preferences.setValue(
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!),
+ preferences.setValue(
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!),
+ preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!),
+ preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!)
+ ]);
+ final Map<String, Object> values = await preferences.getAllWithPrefix('');
+ expect(values['String'], allTestValues['String']);
+ expect(values['Bool'], allTestValues['Bool']);
+ expect(values['Int'], allTestValues['Int']);
+ expect(values['Double'], allTestValues['Double']);
+ expect(values['StringList'], allTestValues['StringList']);
+ expect(values['flutter.String'], allTestValues['flutter.String']);
+ expect(values['flutter.Bool'], allTestValues['flutter.Bool']);
+ expect(values['flutter.Int'], allTestValues['flutter.Int']);
+ expect(values['flutter.Double'], allTestValues['flutter.Double']);
+ expect(values['flutter.StringList'], allTestValues['flutter.StringList']);
+ });
+
+ testWidgets('clearWithNoPrefix', (WidgetTester _) async {
+ await Future.wait(<Future<bool>>[
+ preferences.setValue('String', 'String', allTestValues['String']!),
+ preferences.setValue('Bool', 'Bool', allTestValues['Bool']!),
+ preferences.setValue('Int', 'Int', allTestValues['Int']!),
+ preferences.setValue('Double', 'Double', allTestValues['Double']!),
+ preferences.setValue(
+ 'StringList', 'StringList', allTestValues['StringList']!),
+ preferences.setValue(
+ 'String', 'flutter.String', allTestValues['flutter.String']!),
+ preferences.setValue(
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!),
+ preferences.setValue(
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!),
+ preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!),
+ preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!)
+ ]);
+ await preferences.clearWithPrefix('');
+ final Map<String, Object> values = await preferences.getAllWithPrefix('');
+ expect(values['String'], null);
+ expect(values['Bool'], null);
+ expect(values['Int'], null);
+ expect(values['Double'], null);
+ expect(values['StringList'], null);
+ expect(values['flutter.String'], null);
+ expect(values['flutter.Bool'], null);
+ expect(values['flutter.Int'], null);
+ expect(values['flutter.Double'], null);
+ expect(values['flutter.StringList'], null);
+ });
+
+ testWidgets('getAll', (WidgetTester _) async {
+ await Future.wait(<Future<bool>>[
+ preferences.setValue(
+ 'String', 'flutter.String', allTestValues['flutter.String']!),
+ preferences.setValue(
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!),
+ preferences.setValue(
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!),
+ preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!),
+ preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!)
+ ]);
+ final Map<String, Object> values = await preferences.getAll();
+ expect(values['flutter.String'], allTestValues['flutter.String']);
+ expect(values['flutter.Bool'], allTestValues['flutter.Bool']);
+ expect(values['flutter.Int'], allTestValues['flutter.Int']);
+ expect(values['flutter.Double'], allTestValues['flutter.Double']);
+ expect(values['flutter.StringList'], allTestValues['flutter.StringList']);
+ });
+
+ testWidgets('remove', (WidgetTester _) async {
+ const String key = 'testKey';
+ await preferences.setValue(
+ 'String', key, allTestValues['flutter.String']!);
+ await preferences.setValue('Bool', key, allTestValues['flutter.Bool']!);
+ await preferences.setValue('Int', key, allTestValues['flutter.Int']!);
+ await preferences.setValue(
+ 'Double', key, allTestValues['flutter.Double']!);
+ await preferences.setValue(
+ 'StringList', key, allTestValues['flutter.StringList']!);
+ await preferences.remove(key);
+ final Map<String, Object> values = await preferences.getAllWithPrefix('');
+ expect(values[key], isNull);
+ });
+
+ testWidgets('clear', (WidgetTester _) async {
+ await preferences.setValue(
+ 'String', 'flutter.String', allTestValues['flutter.String']!);
+ await preferences.setValue(
+ 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!);
+ await preferences.setValue(
+ 'Int', 'flutter.Int', allTestValues['flutter.Int']!);
+ await preferences.setValue(
+ 'Double', 'flutter.Double', allTestValues['flutter.Double']!);
+ await preferences.setValue('StringList', 'flutter.StringList',
+ allTestValues['flutter.StringList']!);
+ await preferences.clear();
+ final Map<String, Object> values = await preferences.getAll();
+ expect(values['flutter.String'], null);
+ expect(values['flutter.Bool'], null);
+ expect(values['flutter.Int'], null);
+ expect(values['flutter.Double'], null);
+ expect(values['flutter.StringList'], null);
+ });
+
+ testWidgets('simultaneous writes', (WidgetTester _) async {
+ final List<Future<bool>> writes = <Future<bool>>[];
+ const int writeCount = 100;
+ for (int i = 1; i <= writeCount; i++) {
+ writes.add(preferences.setValue('Int', 'Int', i));
}
- expect(html.window.localStorage.keys, hasLength(kTestValues.length));
- for (final String key in html.window.localStorage.keys) {
- expect(html.window.localStorage[key], json.encode(kTestValues[key]));
- }
-
- // Check that generics are preserved.
- expect((await store.getAll())['flutter.StringList'], isA<List<String>>());
-
- // Invalid key format.
- expect(
- () => store.setValue('String', 'unprefixed', 'hello'),
- throwsA(isA<FormatException>()),
- );
- });
-
- testWidgets('clear', (WidgetTester tester) async {
- final SharedPreferencesPlugin store = SharedPreferencesPlugin();
- html.window.localStorage['flutter.testKey1'] = '"test value"';
- html.window.localStorage['flutter.testKey2'] = '42';
- html.window.localStorage['unprefixed_key'] = 'not a flutter value';
- expect(await store.clear(), isTrue);
- expect(html.window.localStorage.keys.single, 'unprefixed_key');
+ final List<bool> result = await Future.wait(writes, eagerError: true);
+ // All writes should succeed.
+ expect(result.where((bool element) => !element), isEmpty);
+ // The last write should win.
+ final Map<String, Object> values = await preferences.getAllWithPrefix('');
+ expect(values['Int'], writeCount);
});
});
}
diff --git a/packages/shared_preferences/shared_preferences_web/lib/shared_preferences_web.dart b/packages/shared_preferences/shared_preferences_web/lib/shared_preferences_web.dart
index d9d6234..6ee692e 100644
--- a/packages/shared_preferences/shared_preferences_web/lib/shared_preferences_web.dart
+++ b/packages/shared_preferences/shared_preferences_web/lib/shared_preferences_web.dart
@@ -18,19 +18,31 @@
SharedPreferencesStorePlatform.instance = SharedPreferencesPlugin();
}
+ static const String _defaultPrefix = 'flutter.';
+
@override
Future<bool> clear() async {
+ return clearWithPrefix(_defaultPrefix);
+ }
+
+ @override
+ Future<bool> clearWithPrefix(String prefix) async {
// IMPORTANT: Do not use html.window.localStorage.clear() as that will
// remove _all_ local data, not just the keys prefixed with
- // "flutter."
- _storedFlutterKeys.forEach(html.window.localStorage.remove);
+ // _prefix
+ _getStoredFlutterKeys(prefix).forEach(html.window.localStorage.remove);
return true;
}
@override
Future<Map<String, Object>> getAll() async {
+ return getAllWithPrefix(_defaultPrefix);
+ }
+
+ @override
+ Future<Map<String, Object>> getAllWithPrefix(String prefix) async {
final Map<String, Object> allData = <String, Object>{};
- for (final String key in _storedFlutterKeys) {
+ for (final String key in _getStoredFlutterKeys(prefix)) {
allData[key] = _decodeValue(html.window.localStorage[key]!);
}
return allData;
@@ -38,31 +50,19 @@
@override
Future<bool> remove(String key) async {
- _checkPrefix(key);
html.window.localStorage.remove(key);
return true;
}
@override
Future<bool> setValue(String valueType, String key, Object? value) async {
- _checkPrefix(key);
html.window.localStorage[key] = _encodeValue(value);
return true;
}
- void _checkPrefix(String key) {
- if (!key.startsWith('flutter.')) {
- throw FormatException(
- 'Shared preferences keys must start with prefix "flutter.".',
- key,
- 0,
- );
- }
- }
-
- Iterable<String> get _storedFlutterKeys {
+ Iterable<String> _getStoredFlutterKeys(String prefix) {
return html.window.localStorage.keys
- .where((String key) => key.startsWith('flutter.'));
+ .where((String key) => key.startsWith(prefix));
}
String _encodeValue(Object? value) {
diff --git a/packages/shared_preferences/shared_preferences_web/pubspec.yaml b/packages/shared_preferences/shared_preferences_web/pubspec.yaml
index 3598348..5c88356 100644
--- a/packages/shared_preferences/shared_preferences_web/pubspec.yaml
+++ b/packages/shared_preferences/shared_preferences_web/pubspec.yaml
@@ -2,7 +2,7 @@
description: Web platform implementation of shared_preferences
repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_web
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22
-version: 2.0.6
+version: 2.1.0
environment:
sdk: ">=2.17.0 <4.0.0"
@@ -21,7 +21,7 @@
sdk: flutter
flutter_web_plugins:
sdk: flutter
- shared_preferences_platform_interface: ^2.0.0
+ shared_preferences_platform_interface: ^2.2.0
dev_dependencies:
flutter_test:
diff --git a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md
index cbde97a..caeabe4 100644
--- a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md
+++ b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.2.0
+
+* Adds `getAllWithPrefix` and `clearWithPrefix` methods.
+
## 2.1.5
* Clarifies explanation of endorsement in README.
diff --git a/packages/shared_preferences/shared_preferences_windows/example/integration_test/shared_preferences_test.dart b/packages/shared_preferences/shared_preferences_windows/example/integration_test/shared_preferences_test.dart
index 92a34fc..0025200 100644
--- a/packages/shared_preferences/shared_preferences_windows/example/integration_test/shared_preferences_test.dart
+++ b/packages/shared_preferences/shared_preferences_windows/example/integration_test/shared_preferences_test.dart
@@ -41,25 +41,27 @@
final SharedPreferencesWindows preferences = SharedPreferencesWindows();
preferences.clear();
await preferences.setValue(
- 'String', 'String', kTestValues2['flutter.String']!);
- await preferences.setValue('Bool', 'bool', kTestValues2['flutter.bool']!);
- await preferences.setValue('Int', 'int', kTestValues2['flutter.int']!);
+ 'String', 'flutter.String', kTestValues2['flutter.String']!);
await preferences.setValue(
- 'Double', 'double', kTestValues2['flutter.double']!);
+ 'Bool', 'flutter.bool', kTestValues2['flutter.bool']!);
await preferences.setValue(
- 'StringList', 'List', kTestValues2['flutter.List']!);
+ 'Int', 'flutter.int', kTestValues2['flutter.int']!);
+ await preferences.setValue(
+ 'Double', 'flutter.double', kTestValues2['flutter.double']!);
+ await preferences.setValue(
+ 'StringList', 'flutter.List', kTestValues2['flutter.List']!);
final Map<String, Object> values = await preferences.getAll();
- expect(values['String'], kTestValues2['flutter.String']);
- expect(values['bool'], kTestValues2['flutter.bool']);
- expect(values['int'], kTestValues2['flutter.int']);
- expect(values['double'], kTestValues2['flutter.double']);
- expect(values['List'], kTestValues2['flutter.List']);
+ expect(values['flutter.String'], kTestValues2['flutter.String']);
+ expect(values['flutter.bool'], kTestValues2['flutter.bool']);
+ expect(values['flutter.int'], kTestValues2['flutter.int']);
+ expect(values['flutter.double'], kTestValues2['flutter.double']);
+ expect(values['flutter.List'], kTestValues2['flutter.List']);
});
testWidgets('removing', (WidgetTester _) async {
final SharedPreferencesWindows preferences = SharedPreferencesWindows();
preferences.clear();
- const String key = 'testKey';
+ const String key = 'flutter.testKey';
await preferences.setValue('String', key, kTestValues['flutter.String']!);
await preferences.setValue('Bool', key, kTestValues['flutter.bool']!);
await preferences.setValue('Int', key, kTestValues['flutter.int']!);
@@ -75,20 +77,22 @@
final SharedPreferencesWindows preferences = SharedPreferencesWindows();
preferences.clear();
await preferences.setValue(
- 'String', 'String', kTestValues['flutter.String']!);
- await preferences.setValue('Bool', 'bool', kTestValues['flutter.bool']!);
- await preferences.setValue('Int', 'int', kTestValues['flutter.int']!);
+ 'String', 'flutter.String', kTestValues['flutter.String']!);
await preferences.setValue(
- 'Double', 'double', kTestValues['flutter.double']!);
+ 'Bool', 'flutter.bool', kTestValues['flutter.bool']!);
await preferences.setValue(
- 'StringList', 'List', kTestValues['flutter.List']!);
+ 'Int', 'flutter.int', kTestValues['flutter.int']!);
+ await preferences.setValue(
+ 'Double', 'flutter.double', kTestValues['flutter.double']!);
+ await preferences.setValue(
+ 'StringList', 'flutter.List', kTestValues['flutter.List']!);
await preferences.clear();
final Map<String, Object> values = await preferences.getAll();
- expect(values['String'], null);
- expect(values['bool'], null);
- expect(values['int'], null);
- expect(values['double'], null);
- expect(values['List'], null);
+ expect(values['flutter.String'], null);
+ expect(values['flutter.bool'], null);
+ expect(values['flutter.int'], null);
+ expect(values['flutter.double'], null);
+ expect(values['flutter.List'], null);
});
});
}
diff --git a/packages/shared_preferences/shared_preferences_windows/lib/shared_preferences_windows.dart b/packages/shared_preferences/shared_preferences_windows/lib/shared_preferences_windows.dart
index 5cdb30c..6343759 100644
--- a/packages/shared_preferences/shared_preferences_windows/lib/shared_preferences_windows.dart
+++ b/packages/shared_preferences/shared_preferences_windows/lib/shared_preferences_windows.dart
@@ -26,6 +26,8 @@
SharedPreferencesStorePlatform.instance = SharedPreferencesWindows();
}
+ static const String _defaultPrefix = 'flutter.';
+
/// File system used to store to disk. Exposed for testing only.
@visibleForTesting
FileSystem fs = const LocalFileSystem();
@@ -55,10 +57,7 @@
/// Gets the preferences from the stored file. Once read, the preferences are
/// maintained in memory.
- Future<Map<String, Object>> _readPreferences() async {
- if (_cachedPreferences != null) {
- return _cachedPreferences!;
- }
+ Future<Map<String, Object>> _reload() async {
Map<String, Object> preferences = <String, Object>{};
final File? localDataFile = await _getLocalDataFile();
if (localDataFile != null && localDataFile.existsSync()) {
@@ -74,6 +73,10 @@
return preferences;
}
+ Future<Map<String, Object>> _readPreferences() async {
+ return _cachedPreferences ?? await _reload();
+ }
+
/// Writes the cached preferences to disk. Returns [true] if the operation
/// succeeded.
Future<bool> _writePreferences(Map<String, Object> preferences) async {
@@ -97,14 +100,27 @@
@override
Future<bool> clear() async {
+ return clearWithPrefix(_defaultPrefix);
+ }
+
+ @override
+ Future<bool> clearWithPrefix(String prefix) async {
final Map<String, Object> preferences = await _readPreferences();
- preferences.clear();
+ preferences.removeWhere((String key, _) => key.startsWith(prefix));
return _writePreferences(preferences);
}
@override
Future<Map<String, Object>> getAll() async {
- return _readPreferences();
+ return getAllWithPrefix(_defaultPrefix);
+ }
+
+ @override
+ Future<Map<String, Object>> getAllWithPrefix(String prefix) async {
+ final Map<String, Object> withPrefix =
+ Map<String, Object>.from(await _readPreferences());
+ withPrefix.removeWhere((String key, _) => !key.startsWith(prefix));
+ return withPrefix;
}
@override
diff --git a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml
index da7db58..ae2246b 100644
--- a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml
+++ b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml
@@ -2,7 +2,7 @@
description: Windows implementation of shared_preferences
repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_windows
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22
-version: 2.1.5
+version: 2.2.0
environment:
sdk: ">=2.17.0 <4.0.0"
@@ -22,7 +22,7 @@
path: ^1.8.0
path_provider_platform_interface: ^2.0.0
path_provider_windows: ^2.0.0
- shared_preferences_platform_interface: ^2.0.0
+ shared_preferences_platform_interface: ^2.2.0
dev_dependencies:
flutter_test:
diff --git a/packages/shared_preferences/shared_preferences_windows/test/shared_preferences_windows_test.dart b/packages/shared_preferences/shared_preferences_windows/test/shared_preferences_windows_test.dart
index 04fa335..c23ac46 100644
--- a/packages/shared_preferences/shared_preferences_windows/test/shared_preferences_windows_test.dart
+++ b/packages/shared_preferences/shared_preferences_windows/test/shared_preferences_windows_test.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:convert';
+
import 'package:file/memory.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart' as path;
@@ -11,11 +13,43 @@
import 'package:shared_preferences_windows/shared_preferences_windows.dart';
void main() {
- late MemoryFileSystem fileSystem;
+ late MemoryFileSystem fs;
late PathProviderWindows pathProvider;
+ SharedPreferencesWindows.registerWith();
+
+ const Map<String, Object> flutterTestValues = <String, Object>{
+ 'flutter.String': 'hello world',
+ 'flutter.Bool': true,
+ 'flutter.Int': 42,
+ 'flutter.Double': 3.14159,
+ 'flutter.StringList': <String>['foo', 'bar'],
+ };
+
+ const Map<String, Object> prefixTestValues = <String, Object>{
+ 'prefix.String': 'hello world',
+ 'prefix.Bool': true,
+ 'prefix.Int': 42,
+ 'prefix.Double': 3.14159,
+ 'prefix.StringList': <String>['foo', 'bar'],
+ };
+
+ const Map<String, Object> nonPrefixTestValues = <String, Object>{
+ 'String': 'hello world',
+ 'Bool': true,
+ 'Int': 42,
+ 'Double': 3.14159,
+ 'StringList': <String>['foo', 'bar'],
+ };
+
+ final Map<String, Object> allTestValues = <String, Object>{};
+
+ allTestValues.addAll(flutterTestValues);
+ allTestValues.addAll(prefixTestValues);
+ allTestValues.addAll(nonPrefixTestValues);
+
setUp(() {
- fileSystem = MemoryFileSystem.test();
+ fs = MemoryFileSystem.test();
pathProvider = FakePathProviderWindows();
});
@@ -25,18 +59,18 @@
}
Future<void> writeTestFile(String value) async {
- fileSystem.file(await getFilePath())
+ fs.file(await getFilePath())
..createSync(recursive: true)
..writeAsStringSync(value);
}
Future<String> readTestFile() async {
- return fileSystem.file(await getFilePath()).readAsStringSync();
+ return fs.file(await getFilePath()).readAsStringSync();
}
SharedPreferencesWindows getPreferences() {
final SharedPreferencesWindows prefs = SharedPreferencesWindows();
- prefs.fs = fileSystem;
+ prefs.fs = fs;
prefs.pathProvider = pathProvider;
return prefs;
}
@@ -48,13 +82,21 @@
});
test('getAll', () async {
- await writeTestFile('{"key1": "one", "key2": 2}');
+ await writeTestFile(json.encode(allTestValues));
final SharedPreferencesWindows prefs = getPreferences();
final Map<String, Object> values = await prefs.getAll();
- expect(values, hasLength(2));
- expect(values['key1'], 'one');
- expect(values['key2'], 2);
+ expect(values, hasLength(5));
+ expect(values, flutterTestValues);
+ });
+
+ test('getAllWithPrefix', () async {
+ await writeTestFile(json.encode(allTestValues));
+ final SharedPreferencesWindows prefs = getPreferences();
+
+ final Map<String, Object> values = await prefs.getAllWithPrefix('prefix.');
+ expect(values, hasLength(5));
+ expect(values, prefixTestValues);
});
test('remove', () async {
@@ -77,12 +119,43 @@
});
test('clear', () async {
- await writeTestFile('{"key1":"one","key2":2}');
+ await writeTestFile(json.encode(flutterTestValues));
final SharedPreferencesWindows prefs = getPreferences();
+ expect(await readTestFile(), json.encode(flutterTestValues));
await prefs.clear();
expect(await readTestFile(), '{}');
});
+
+ test('clearWithPrefix', () async {
+ await writeTestFile(json.encode(flutterTestValues));
+ final SharedPreferencesWindows prefs = getPreferences();
+ await prefs.clearWithPrefix('prefix.');
+ final Map<String, Object> noValues =
+ await prefs.getAllWithPrefix('prefix.');
+ expect(noValues, hasLength(0));
+
+ final Map<String, Object> values = await prefs.getAll();
+ expect(values, hasLength(5));
+ expect(values, flutterTestValues);
+ });
+
+ test('getAllWithNoPrefix', () async {
+ await writeTestFile(json.encode(allTestValues));
+ final SharedPreferencesWindows prefs = getPreferences();
+
+ final Map<String, Object> values = await prefs.getAllWithPrefix('');
+ expect(values, hasLength(15));
+ expect(values, allTestValues);
+ });
+
+ test('clearWithNoPrefix', () async {
+ await writeTestFile(json.encode(flutterTestValues));
+ final SharedPreferencesWindows prefs = getPreferences();
+ await prefs.clearWithPrefix('');
+ final Map<String, Object> noValues = await prefs.getAllWithPrefix('');
+ expect(noValues, hasLength(0));
+ });
}
/// Fake implementation of PathProviderWindows that returns hard-coded paths,