Skip to content

Commit 5cbea7b

Browse files
authored
[shared_preferences] allow custom key prefixes - platform changes (#3596)
[shared_preferences] allow custom key prefixes - platform changes
1 parent 7781aee commit 5cbea7b

File tree

31 files changed

+1353
-415
lines changed

31 files changed

+1353
-415
lines changed

packages/shared_preferences/shared_preferences_android/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.1.0
2+
3+
* Adds `getAllWithPrefix` and `clearWithPrefix` methods.
4+
15
## 2.0.17
26

37
* Clarifies explanation of endorsement in README.

packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/MethodCallHandlerImpl.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,16 +106,18 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
106106
// We've been committing the whole time.
107107
result.success(true);
108108
break;
109-
case "getAll":
110-
result.success(getAllPrefs());
109+
case "getAllWithPrefix":
110+
String prefix = call.argument("prefix");
111+
result.success(getAllPrefs(prefix));
111112
return;
112113
case "remove":
113114
commitAsync(preferences.edit().remove(key), result);
114115
break;
115-
case "clear":
116-
Set<String> keySet = getAllPrefs().keySet();
116+
case "clearWithPrefix":
117+
String newPrefix = call.argument("prefix");
118+
Set<String> keys = getAllPrefs(newPrefix).keySet();
117119
SharedPreferences.Editor clearEditor = preferences.edit();
118-
for (String keyToDelete : keySet) {
120+
for (String keyToDelete : keys) {
119121
clearEditor.remove(keyToDelete);
120122
}
121123
commitAsync(clearEditor, result);
@@ -181,12 +183,12 @@ private String encodeList(List<String> list) throws IOException {
181183
}
182184
}
183185

184-
// Filter preferences to only those set by the flutter app.
185-
private Map<String, Object> getAllPrefs() throws IOException {
186+
// Gets all shared preferences, filtered to only those set with the given prefix.
187+
private Map<String, Object> getAllPrefs(String prefix) throws IOException {
186188
Map<String, ?> allPrefs = preferences.getAll();
187189
Map<String, Object> filteredPrefs = new HashMap<>();
188190
for (String key : allPrefs.keySet()) {
189-
if (key.startsWith("flutter.")) {
191+
if (key.startsWith(prefix)) {
190192
Object value = allPrefs.get(key);
191193
if (value instanceof String) {
192194
String stringValue = (String) value;

packages/shared_preferences/shared_preferences_android/example/integration_test/shared_preferences_test.dart

Lines changed: 207 additions & 60 deletions
Large diffs are not rendered by default.

packages/shared_preferences/shared_preferences_android/lib/shared_preferences_android.dart

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class SharedPreferencesAndroid extends SharedPreferencesStorePlatform {
1919
SharedPreferencesStorePlatform.instance = SharedPreferencesAndroid();
2020
}
2121

22+
static const String _defaultPrefix = 'flutter.';
23+
2224
@override
2325
Future<bool> remove(String key) async {
2426
return (await _kChannel.invokeMethod<bool>(
@@ -37,17 +39,28 @@ class SharedPreferencesAndroid extends SharedPreferencesStorePlatform {
3739

3840
@override
3941
Future<bool> clear() async {
40-
return (await _kChannel.invokeMethod<bool>('clear'))!;
42+
return clearWithPrefix(_defaultPrefix);
43+
}
44+
45+
@override
46+
Future<bool> clearWithPrefix(String prefix) async {
47+
return (await _kChannel.invokeMethod<bool>(
48+
'clearWithPrefix',
49+
<String, dynamic>{'prefix': prefix},
50+
))!;
4151
}
4252

4353
@override
4454
Future<Map<String, Object>> getAll() async {
45-
final Map<String, Object>? preferences =
46-
await _kChannel.invokeMapMethod<String, Object>('getAll');
55+
return getAllWithPrefix(_defaultPrefix);
56+
}
4757

48-
if (preferences == null) {
49-
return <String, Object>{};
50-
}
51-
return preferences;
58+
@override
59+
Future<Map<String, Object>> getAllWithPrefix(String prefix) async {
60+
return (await _kChannel.invokeMapMethod<String, Object>(
61+
'getAllWithPrefix',
62+
<String, dynamic>{'prefix': prefix},
63+
)) ??
64+
<String, Object>{};
5265
}
5366
}

packages/shared_preferences/shared_preferences_android/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: shared_preferences_android
22
description: Android implementation of the shared_preferences plugin
33
repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_android
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22
5-
version: 2.0.17
5+
version: 2.1.0
66

77
environment:
88
sdk: ">=2.17.0 <4.0.0"
@@ -20,7 +20,7 @@ flutter:
2020
dependencies:
2121
flutter:
2222
sdk: flutter
23-
shared_preferences_platform_interface: ^2.0.0
23+
shared_preferences_platform_interface: ^2.2.0
2424

2525
dev_dependencies:
2626
flutter_test:

packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,36 @@ void main() {
1616
'plugins.flutter.io/shared_preferences_android',
1717
);
1818

19-
const Map<String, Object> kTestValues = <String, Object>{
19+
const Map<String, Object> flutterTestValues = <String, Object>{
2020
'flutter.String': 'hello world',
2121
'flutter.Bool': true,
2222
'flutter.Int': 42,
2323
'flutter.Double': 3.14159,
2424
'flutter.StringList': <String>['foo', 'bar'],
2525
};
26-
// Create a dummy in-memory implementation to back the mocked method channel
27-
// API to simplify validation of the expected calls.
26+
27+
const Map<String, Object> prefixTestValues = <String, Object>{
28+
'prefix.String': 'hello world',
29+
'prefix.Bool': true,
30+
'prefix.Int': 42,
31+
'prefix.Double': 3.14159,
32+
'prefix.StringList': <String>['foo', 'bar'],
33+
};
34+
35+
const Map<String, Object> nonPrefixTestValues = <String, Object>{
36+
'String': 'hello world',
37+
'Bool': true,
38+
'Int': 42,
39+
'Double': 3.14159,
40+
'StringList': <String>['foo', 'bar'],
41+
};
42+
43+
final Map<String, Object> allTestValues = <String, Object>{};
44+
45+
allTestValues.addAll(flutterTestValues);
46+
allTestValues.addAll(prefixTestValues);
47+
allTestValues.addAll(nonPrefixTestValues);
48+
2849
late InMemorySharedPreferencesStore testData;
2950

3051
final List<MethodCall> log = <MethodCall>[];
@@ -45,6 +66,12 @@ void main() {
4566
if (methodCall.method == 'getAll') {
4667
return testData.getAll();
4768
}
69+
if (methodCall.method == 'getAllWithPrefix') {
70+
final Map<String, Object?> arguments =
71+
getArgumentDictionary(methodCall);
72+
final String prefix = arguments['prefix']! as String;
73+
return testData.getAllWithPrefix(prefix);
74+
}
4875
if (methodCall.method == 'remove') {
4976
final Map<String, Object?> arguments =
5077
getArgumentDictionary(methodCall);
@@ -54,6 +81,12 @@ void main() {
5481
if (methodCall.method == 'clear') {
5582
return testData.clear();
5683
}
84+
if (methodCall.method == 'clearWithPrefix') {
85+
final Map<String, Object?> arguments =
86+
getArgumentDictionary(methodCall);
87+
final String prefix = arguments['prefix']! as String;
88+
return testData.clearWithPrefix(prefix);
89+
}
5790
final RegExp setterRegExp = RegExp(r'set(.*)');
5891
final Match? match = setterRegExp.matchAsPrefix(methodCall.method);
5992
if (match?.groupCount == 1) {
@@ -77,14 +110,21 @@ void main() {
77110

78111
test('getAll', () async {
79112
store = SharedPreferencesAndroid();
80-
testData = InMemorySharedPreferencesStore.withData(kTestValues);
81-
expect(await store.getAll(), kTestValues);
82-
expect(log.single.method, 'getAll');
113+
testData = InMemorySharedPreferencesStore.withData(allTestValues);
114+
expect(await store.getAll(), flutterTestValues);
115+
expect(log.single.method, 'getAllWithPrefix');
116+
});
117+
118+
test('getAllWithPrefix', () async {
119+
store = SharedPreferencesAndroid();
120+
testData = InMemorySharedPreferencesStore.withData(allTestValues);
121+
expect(await store.getAllWithPrefix('prefix.'), prefixTestValues);
122+
expect(log.single.method, 'getAllWithPrefix');
83123
});
84124

85125
test('remove', () async {
86126
store = SharedPreferencesAndroid();
87-
testData = InMemorySharedPreferencesStore.withData(kTestValues);
127+
testData = InMemorySharedPreferencesStore.withData(allTestValues);
88128
expect(await store.remove('flutter.String'), true);
89129
expect(await store.remove('flutter.Bool'), true);
90130
expect(await store.remove('flutter.Int'), true);
@@ -102,13 +142,13 @@ void main() {
102142
test('setValue', () async {
103143
store = SharedPreferencesAndroid();
104144
expect(await testData.getAll(), isEmpty);
105-
for (final String key in kTestValues.keys) {
106-
final Object value = kTestValues[key]!;
145+
for (final String key in allTestValues.keys) {
146+
final Object value = allTestValues[key]!;
107147
expect(await store.setValue(key.split('.').last, key, value), true);
108148
}
109-
expect(await testData.getAll(), kTestValues);
149+
expect(await testData.getAll(), flutterTestValues);
110150

111-
expect(log, hasLength(5));
151+
expect(log, hasLength(15));
112152
expect(log[0].method, 'setString');
113153
expect(log[1].method, 'setBool');
114154
expect(log[2].method, 'setInt');
@@ -118,11 +158,36 @@ void main() {
118158

119159
test('clear', () async {
120160
store = SharedPreferencesAndroid();
121-
testData = InMemorySharedPreferencesStore.withData(kTestValues);
161+
testData = InMemorySharedPreferencesStore.withData(allTestValues);
122162
expect(await testData.getAll(), isNotEmpty);
123163
expect(await store.clear(), true);
124164
expect(await testData.getAll(), isEmpty);
125-
expect(log.single.method, 'clear');
165+
expect(log.single.method, 'clearWithPrefix');
166+
});
167+
168+
test('clearWithPrefix', () async {
169+
store = SharedPreferencesAndroid();
170+
testData = InMemorySharedPreferencesStore.withData(allTestValues);
171+
172+
expect(await testData.getAllWithPrefix('prefix.'), isNotEmpty);
173+
expect(await store.clearWithPrefix('prefix.'), true);
174+
expect(await testData.getAllWithPrefix('prefix.'), isEmpty);
175+
});
176+
177+
test('getAllWithNoPrefix', () async {
178+
store = SharedPreferencesAndroid();
179+
testData = InMemorySharedPreferencesStore.withData(allTestValues);
180+
181+
expect(await testData.getAllWithPrefix(''), hasLength(15));
182+
});
183+
184+
test('clearWithNoPrefix', () async {
185+
store = SharedPreferencesAndroid();
186+
testData = InMemorySharedPreferencesStore.withData(allTestValues);
187+
188+
expect(await testData.getAllWithPrefix(''), isNotEmpty);
189+
expect(await store.clearWithPrefix(''), true);
190+
expect(await testData.getAllWithPrefix(''), isEmpty);
126191
});
127192
});
128193
}

packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.2.0
2+
3+
* Adds `getAllWithPrefix` and `clearWithPrefix` methods.
4+
15
## 2.1.5
26

37
* Clarifies explanation of endorsement in README.

packages/shared_preferences/shared_preferences_foundation/darwin/Classes/SharedPreferencesPlugin.swift

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ public class SharedPreferencesPlugin: NSObject, FlutterPlugin, UserDefaultsApi {
2222
UserDefaultsApiSetup.setUp(binaryMessenger: messenger, api: instance)
2323
}
2424

25-
func getAll() -> [String? : Any?] {
26-
return getAllPrefs();
25+
func getAllWithPrefix(prefix: String) -> [String? : Any?] {
26+
return getAllPrefs(prefix: prefix)
2727
}
2828

2929
func setBool(key: String, value: Bool) {
@@ -42,23 +42,23 @@ public class SharedPreferencesPlugin: NSObject, FlutterPlugin, UserDefaultsApi {
4242
UserDefaults.standard.removeObject(forKey: key)
4343
}
4444

45-
func clear() {
45+
func clearWithPrefix(prefix: String) {
4646
let defaults = UserDefaults.standard
47-
for (key, _) in getAllPrefs() {
47+
for (key, _) in getAllPrefs(prefix: prefix) {
4848
defaults.removeObject(forKey: key)
4949
}
5050
}
51-
}
5251

53-
/// Returns all preferences stored by this plugin.
54-
private func getAllPrefs() -> [String: Any] {
55-
var filteredPrefs: [String: Any] = [:]
56-
if let appDomain = Bundle.main.bundleIdentifier,
57-
let prefs = UserDefaults.standard.persistentDomain(forName: appDomain)
58-
{
59-
for (key, value) in prefs where key.hasPrefix("flutter.") {
60-
filteredPrefs[key] = value
52+
/// Returns all preferences stored with specified prefix.
53+
func getAllPrefs(prefix: String) -> [String: Any] {
54+
var filteredPrefs: [String: Any] = [:]
55+
if let appDomain = Bundle.main.bundleIdentifier,
56+
let prefs = UserDefaults.standard.persistentDomain(forName: appDomain)
57+
{
58+
for (key, value) in prefs where key.hasPrefix(prefix) {
59+
filteredPrefs[key] = value
60+
}
6161
}
62+
return filteredPrefs
6263
}
63-
return filteredPrefs
6464
}

0 commit comments

Comments
 (0)