[shared_preferences] allow custom key prefixes platform only changes (#3497)

[shared_preferences] allow custom key prefixes platform only changes
diff --git a/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md b/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md
index 43b0166..5f29cb7 100644
--- a/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md
+++ b/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 2.2.0
 
+* Adds `getAllWithPrefix` and `clearWithPrefix` method.
 * Aligns Dart and Flutter SDK constraints.
 
 ## 2.1.1
diff --git a/packages/shared_preferences/shared_preferences_platform_interface/lib/method_channel_shared_preferences.dart b/packages/shared_preferences/shared_preferences_platform_interface/lib/method_channel_shared_preferences.dart
index 2974f0a..5c415b1 100644
--- a/packages/shared_preferences/shared_preferences_platform_interface/lib/method_channel_shared_preferences.dart
+++ b/packages/shared_preferences/shared_preferences_platform_interface/lib/method_channel_shared_preferences.dart
@@ -39,13 +39,25 @@
   }
 
   @override
-  Future<Map<String, Object>> getAll() async {
-    final Map<String, Object>? preferences =
-        await _kChannel.invokeMapMethod<String, Object>('getAll');
+  Future<bool> clearWithPrefix(String prefix) async {
+    return (await _kChannel.invokeMethod<bool>(
+      'clearWithPrefix',
+      <String, dynamic>{'prefix': prefix},
+    ))!;
+  }
 
-    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>{};
+  }
+
+  @override
+  Future<Map<String, Object>> getAll() async {
+    return await _kChannel.invokeMapMethod<String, Object>('getAll') ??
+        <String, Object>{};
   }
 }
diff --git a/packages/shared_preferences/shared_preferences_platform_interface/lib/shared_preferences_platform_interface.dart b/packages/shared_preferences/shared_preferences_platform_interface/lib/shared_preferences_platform_interface.dart
index ced6aa5..c9e4aa6 100644
--- a/packages/shared_preferences/shared_preferences_platform_interface/lib/shared_preferences_platform_interface.dart
+++ b/packages/shared_preferences/shared_preferences_platform_interface/lib/shared_preferences_platform_interface.dart
@@ -62,11 +62,29 @@
   /// * Value type "StringList" must be passed if the value is of type `List<String>`.
   Future<bool> setValue(String valueType, String key, Object value);
 
-  /// Removes all keys and values in the store.
+  /// Removes all keys and values in the store where the key starts with 'flutter.'.
+  ///
+  /// This default behavior is for backwards compatibility with older versions of this
+  /// plugin, which did not support custom prefixes, and instead always used the
+  /// prefix 'flutter.'.
   Future<bool> clear();
 
-  /// Returns all key/value pairs persisted in this store.
+  /// Removes all keys and values in the store with given prefix.
+  Future<bool> clearWithPrefix(String prefix) {
+    throw UnimplementedError('clearWithPrefix is not implemented.');
+  }
+
+  /// Returns all key/value pairs persisted in this store where the key starts with 'flutter.'.
+  ///
+  /// This default behavior is for backwards compatibility with older versions of this
+  /// plugin, which did not support custom prefixes, and instead always used the
+  /// prefix 'flutter.'.
   Future<Map<String, Object>> getAll();
+
+  /// Returns all key/value pairs persisting in this store that have given [prefix].
+  Future<Map<String, Object>> getAllWithPrefix(String prefix) {
+    throw UnimplementedError('getAllWithPrefix is not implemented.');
+  }
 }
 
 /// Stores data in memory.
@@ -81,16 +99,29 @@
       : _data = Map<String, Object>.from(data);
 
   final Map<String, Object> _data;
+  static const String _defaultPrefix = 'flutter.';
 
   @override
   Future<bool> clear() async {
-    _data.clear();
+    return clearWithPrefix(_defaultPrefix);
+  }
+
+  @override
+  Future<bool> clearWithPrefix(String prefix) async {
+    _data.removeWhere((String key, _) => key.startsWith(prefix));
     return true;
   }
 
   @override
   Future<Map<String, Object>> getAll() async {
-    return Map<String, Object>.from(_data);
+    return getAllWithPrefix(_defaultPrefix);
+  }
+
+  @override
+  Future<Map<String, Object>> getAllWithPrefix(String prefix) async {
+    final Map<String, Object> preferences = Map<String, Object>.from(_data);
+    preferences.removeWhere((String key, _) => !key.startsWith(prefix));
+    return preferences;
   }
 
   @override
diff --git a/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml b/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml
index 589a632..15e5e59 100644
--- a/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml
+++ b/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A common platform interface for the shared_preferences plugin.
 repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_platform_interface
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22
-version: 2.1.1
+version: 2.2.0
 
 environment:
   sdk: ">=2.17.0 <4.0.0"
diff --git a/packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart b/packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart
index 296592e..a13ef5c 100644
--- a/packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart
+++ b/packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart
@@ -15,7 +15,7 @@
       'plugins.flutter.io/shared_preferences',
     );
 
-    const Map<String, Object> kTestValues = <String, Object>{
+    const Map<String, Object> flutterTestValues = <String, Object>{
       'flutter.String': 'hello world',
       'flutter.Bool': true,
       'flutter.Int': 42,
@@ -23,6 +23,28 @@
       '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);
+
     late InMemorySharedPreferencesStore testData;
 
     final List<MethodCall> log = <MethodCall>[];
@@ -43,6 +65,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);
@@ -52,6 +80,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) {
@@ -73,13 +107,19 @@
     });
 
     test('getAll', () async {
-      testData = InMemorySharedPreferencesStore.withData(kTestValues);
-      expect(await store.getAll(), kTestValues);
+      testData = InMemorySharedPreferencesStore.withData(allTestValues);
+      expect(await store.getAll(), flutterTestValues);
       expect(log.single.method, 'getAll');
     });
 
+    test('getAllWithPrefix', () async {
+      testData = InMemorySharedPreferencesStore.withData(allTestValues);
+      expect(await store.getAllWithPrefix('prefix.'), prefixTestValues);
+      expect(log.single.method, 'getAllWithPrefix');
+    });
+
     test('remove', () async {
-      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);
@@ -96,13 +136,13 @@
 
     test('setValue', () async {
       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');
@@ -111,12 +151,34 @@
     });
 
     test('clear', () async {
-      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');
     });
+
+    test('clearWithPrefix', () async {
+      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 {
+      testData = InMemorySharedPreferencesStore.withData(allTestValues);
+
+      expect(await testData.getAllWithPrefix(''), hasLength(15));
+    });
+
+    test('clearWithNoPrefix', () async {
+      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_platform_interface/test/shared_preferences_platform_interface_test.dart b/packages/shared_preferences/shared_preferences_platform_interface/test/shared_preferences_platform_interface_test.dart
index ed078e8..01b3754 100644
--- a/packages/shared_preferences/shared_preferences_platform_interface/test/shared_preferences_platform_interface_test.dart
+++ b/packages/shared_preferences/shared_preferences_platform_interface/test/shared_preferences_platform_interface_test.dart
@@ -47,11 +47,21 @@
   }
 
   @override
+  Future<bool> clearWithPrefix(String prefix) {
+    throw UnimplementedError();
+  }
+
+  @override
   Future<Map<String, Object>> getAll() {
     throw UnimplementedError();
   }
 
   @override
+  Future<Map<String, Object>> getAllWithPrefix(String prefix) {
+    throw UnimplementedError();
+  }
+
+  @override
   Future<bool> remove(String key) {
     throw UnimplementedError();
   }
@@ -72,11 +82,21 @@
   }
 
   @override
+  Future<bool> clearWithPrefix(String prefix) {
+    throw UnimplementedError();
+  }
+
+  @override
   Future<Map<String, Object>> getAll() {
     throw UnimplementedError();
   }
 
   @override
+  Future<Map<String, Object>> getAllWithPrefix(String prefix) {
+    throw UnimplementedError();
+  }
+
+  @override
   Future<bool> remove(String key) {
     throw UnimplementedError();
   }
@@ -99,11 +119,21 @@
   }
 
   @override
+  Future<bool> clearWithPrefix(String prefix) {
+    throw UnimplementedError();
+  }
+
+  @override
   Future<Map<String, Object>> getAll() {
     throw UnimplementedError();
   }
 
   @override
+  Future<Map<String, Object>> getAllWithPrefix(String prefix) {
+    throw UnimplementedError();
+  }
+
+  @override
   Future<bool> remove(String key) {
     throw UnimplementedError();
   }