Skip to content

Commit

Permalink
[shared_preferences] allow custom key prefixes - platform changes (fl…
Browse files Browse the repository at this point in the history
…utter#3596)

[shared_preferences] allow custom key prefixes - platform changes
  • Loading branch information
tarrinneal committed Mar 31, 2023
1 parent 7781aee commit 5cbea7b
Show file tree
Hide file tree
Showing 31 changed files with 1,353 additions and 415 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.1.0

* Adds `getAllWithPrefix` and `clearWithPrefix` methods.

## 2.0.17

* Clarifies explanation of endorsement in README.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,18 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
// 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);
Expand Down Expand Up @@ -181,12 +183,12 @@ private String encodeList(List<String> list) throws IOException {
}
}

// 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;
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class SharedPreferencesAndroid extends SharedPreferencesStorePlatform {
SharedPreferencesStorePlatform.instance = SharedPreferencesAndroid();
}

static const String _defaultPrefix = 'flutter.';

@override
Future<bool> remove(String key) async {
return (await _kChannel.invokeMethod<bool>(
Expand All @@ -37,17 +39,28 @@ class SharedPreferencesAndroid extends SharedPreferencesStorePlatform {

@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>{};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: shared_preferences_android
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"
Expand All @@ -20,7 +20,7 @@ flutter:
dependencies:
flutter:
sdk: flutter
shared_preferences_platform_interface: ^2.0.0
shared_preferences_platform_interface: ^2.2.0

dev_dependencies:
flutter_test:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,36 @@ void main() {
'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>[];
Expand All @@ -45,6 +66,12 @@ void main() {
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);
Expand All @@ -54,6 +81,12 @@ void main() {
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) {
Expand All @@ -77,14 +110,21 @@ void main() {

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);
Expand All @@ -102,13 +142,13 @@ void main() {
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');
Expand All @@ -118,11 +158,36 @@ void main() {

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);
});
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.2.0

* Adds `getAllWithPrefix` and `clearWithPrefix` methods.

## 2.1.5

* Clarifies explanation of endorsement in README.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public class SharedPreferencesPlugin: NSObject, FlutterPlugin, UserDefaultsApi {
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) {
Expand All @@ -42,23 +42,23 @@ public class SharedPreferencesPlugin: NSObject, FlutterPlugin, UserDefaultsApi {
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
}
Loading

0 comments on commit 5cbea7b

Please sign in to comment.