Implement shared_preferences on top of platform interface (#2325)

* implement shared_preferences on top of platform interface
diff --git a/.gitignore b/.gitignore
index 7341696..54133a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,7 @@
 gradlew
 gradlew.bat
 gradle-wrapper.jar
+.flutter-plugins-dependencies
 *.iml
 
 GeneratedPluginRegistrant.h
diff --git a/packages/shared_preferences/analysis_options.yaml b/packages/shared_preferences/analysis_options.yaml
deleted file mode 100644
index 35344bf..0000000
--- a/packages/shared_preferences/analysis_options.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-# This is a temporary file to allow us to land a new set of linter rules in a
-# series of manageable patches instead of one gigantic PR. It disables some of
-# the new lints that are already failing on this plugin, for this plugin. It
-# should be deleted and the failing lints addressed as soon as possible.
-
-include: ../../analysis_options.yaml
-
-analyzer:
-  errors:
-    curly_braces_in_flow_control_structures: ignore
-    unawaited_futures: ignore
diff --git a/packages/shared_preferences/shared_preferences/CHANGELOG.md b/packages/shared_preferences/shared_preferences/CHANGELOG.md
index 15a5047..487ec1d 100644
--- a/packages/shared_preferences/shared_preferences/CHANGELOG.md
+++ b/packages/shared_preferences/shared_preferences/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.5.4+8
+
+* Switch `package:shared_preferences` to `package:shared_preferences_platform_interface`.
+  No code changes are necessary in Flutter apps. This is not a breaking change.
+
 ## 0.5.4+7
 
 * Restructure the project for Web support.
diff --git a/packages/shared_preferences/shared_preferences/example/lib/main.dart b/packages/shared_preferences/shared_preferences/example/lib/main.dart
index 5c7f0b0..46daeff 100644
--- a/packages/shared_preferences/shared_preferences/example/lib/main.dart
+++ b/packages/shared_preferences/shared_preferences/example/lib/main.dart
@@ -67,13 +67,14 @@
                   case ConnectionState.waiting:
                     return const CircularProgressIndicator();
                   default:
-                    if (snapshot.hasError)
+                    if (snapshot.hasError) {
                       return Text('Error: ${snapshot.error}');
-                    else
+                    } else {
                       return Text(
                         'Button tapped ${snapshot.data} time${snapshot.data == 1 ? '' : 's'}.\n\n'
                         'This should persist across restarts.',
                       );
+                    }
                 }
               })),
       floatingActionButton: FloatingActionButton(
diff --git a/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e.dart b/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e.dart
index 1c09e2e..b693df2 100644
--- a/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e.dart
+++ b/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e.dart
@@ -63,23 +63,21 @@
 
     test('removing', () async {
       const String key = 'testKey';
-      preferences
-        ..setString(key, kTestValues['flutter.String'])
-        ..setBool(key, kTestValues['flutter.bool'])
-        ..setInt(key, kTestValues['flutter.int'])
-        ..setDouble(key, kTestValues['flutter.double'])
-        ..setStringList(key, kTestValues['flutter.List']);
+      await preferences.setString(key, kTestValues['flutter.String']);
+      await preferences.setBool(key, kTestValues['flutter.bool']);
+      await preferences.setInt(key, kTestValues['flutter.int']);
+      await preferences.setDouble(key, kTestValues['flutter.double']);
+      await preferences.setStringList(key, kTestValues['flutter.List']);
       await preferences.remove(key);
       expect(preferences.get('testKey'), isNull);
     });
 
     test('clearing', () async {
-      preferences
-        ..setString('String', kTestValues['flutter.String'])
-        ..setBool('bool', kTestValues['flutter.bool'])
-        ..setInt('int', kTestValues['flutter.int'])
-        ..setDouble('double', kTestValues['flutter.double'])
-        ..setStringList('List', kTestValues['flutter.List']);
+      await preferences.setString('String', kTestValues['flutter.String']);
+      await preferences.setBool('bool', kTestValues['flutter.bool']);
+      await preferences.setInt('int', kTestValues['flutter.int']);
+      await preferences.setDouble('double', kTestValues['flutter.double']);
+      await preferences.setStringList('List', kTestValues['flutter.List']);
       await preferences.clear();
       expect(preferences.getString('String'), null);
       expect(preferences.getBool('bool'), null);
diff --git a/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e_test.dart b/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e_test.dart
index ff6e9ce..f3aa9e2 100644
--- a/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e_test.dart
+++ b/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e_test.dart
@@ -10,6 +10,6 @@
   final FlutterDriver driver = await FlutterDriver.connect();
   final String result =
       await driver.requestData(null, timeout: const Duration(minutes: 1));
-  driver.close();
+  await driver.close();
   exit(result == 'pass' ? 0 : 1);
 }
diff --git a/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart b/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart
index c82116a..62160de 100644
--- a/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart
+++ b/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart
@@ -4,11 +4,9 @@
 
 import 'dart:async';
 
-import 'package:flutter/services.dart';
 import 'package:meta/meta.dart';
 
-const MethodChannel _kChannel =
-    MethodChannel('plugins.flutter.io/shared_preferences');
+import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart';
 
 /// Wraps NSUserDefaults (on iOS) and SharedPreferences (on Android), providing
 /// a persistent store for simple data.
@@ -20,6 +18,9 @@
   static const String _prefix = 'flutter.';
   static Completer<SharedPreferences> _completer;
 
+  static SharedPreferencesStorePlatform get _store =>
+      SharedPreferencesStorePlatform.instance;
+
   /// Loads and parses the [SharedPreferences] for this app from disk.
   ///
   /// Because this is reading from disk, it shouldn't be awaited in
@@ -124,14 +125,10 @@
   Future<bool> remove(String key) => _setValue(null, key, null);
 
   Future<bool> _setValue(String valueType, String key, Object value) {
-    final Map<String, dynamic> params = <String, dynamic>{
-      'key': '$_prefix$key',
-    };
+    final String prefixedKey = '$_prefix$key';
     if (value == null) {
       _preferenceCache.remove(key);
-      return _kChannel
-          .invokeMethod<bool>('remove', params)
-          .then<bool>((dynamic result) => result);
+      return _store.remove(prefixedKey);
     } else {
       if (value is List<String>) {
         // Make a copy of the list so that later mutations won't propagate
@@ -139,22 +136,19 @@
       } else {
         _preferenceCache[key] = value;
       }
-      params['value'] = value;
-      return _kChannel
-          .invokeMethod<bool>('set$valueType', params)
-          .then<bool>((dynamic result) => result);
+      return _store.setValue(valueType, prefixedKey, value);
     }
   }
 
   /// Always returns true.
   /// On iOS, synchronize is marked deprecated. On Android, we commit every set.
   @deprecated
-  Future<bool> commit() async => await _kChannel.invokeMethod<bool>('commit');
+  Future<bool> commit() async => true;
 
   /// Completes with true once the user preferences for the app has been cleared.
-  Future<bool> clear() async {
+  Future<bool> clear() {
     _preferenceCache.clear();
-    return await _kChannel.invokeMethod<bool>('clear');
+    return _store.clear();
   }
 
   /// Fetches the latest values from the host platform.
@@ -169,8 +163,7 @@
   }
 
   static Future<Map<String, Object>> _getSharedPreferencesMap() async {
-    final Map<String, Object> fromSystem =
-        await _kChannel.invokeMapMethod<String, Object>('getAll');
+    final Map<String, Object> fromSystem = await _store.getAll();
     assert(fromSystem != null);
     // Strip the flutter. prefix from the returned preferences.
     final Map<String, Object> preferencesMap = <String, Object>{};
@@ -194,12 +187,8 @@
       }
       return MapEntry<String, dynamic>(newKey, value);
     });
-    _kChannel.setMockMethodCallHandler((MethodCall methodCall) async {
-      if (methodCall.method == 'getAll') {
-        return newValues;
-      }
-      return null;
-    });
+    SharedPreferencesStorePlatform.instance =
+        InMemorySharedPreferencesStore.withData(newValues);
     _completer = null;
   }
 }
diff --git a/packages/shared_preferences/shared_preferences/pubspec.yaml b/packages/shared_preferences/shared_preferences/pubspec.yaml
index 6285394..c9f6d33 100644
--- a/packages/shared_preferences/shared_preferences/pubspec.yaml
+++ b/packages/shared_preferences/shared_preferences/pubspec.yaml
@@ -3,7 +3,7 @@
   Wraps NSUserDefaults on iOS and SharedPreferences on Android.
 author: Flutter Team <flutter-dev@googlegroups.com>
 homepage: https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences
-version: 0.5.4+7
+version: 0.5.4+8
 
 flutter:
   plugin:
@@ -15,6 +15,7 @@
   meta: ^1.0.4
   flutter:
     sdk: flutter
+  shared_preferences_platform_interface: ^1.0.0
 
 dev_dependencies:
   flutter_test:
diff --git a/packages/shared_preferences/shared_preferences/test/shared_preferences_test.dart b/packages/shared_preferences/shared_preferences/test/shared_preferences_test.dart
index d171f7a..b219774 100755
--- a/packages/shared_preferences/shared_preferences/test/shared_preferences_test.dart
+++ b/packages/shared_preferences/shared_preferences/test/shared_preferences_test.dart
@@ -5,15 +5,12 @@
 import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:shared_preferences/shared_preferences.dart';
+import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart';
 
 void main() {
   TestWidgetsFlutterBinding.ensureInitialized();
 
-  group('$SharedPreferences', () {
-    const MethodChannel channel = MethodChannel(
-      'plugins.flutter.io/shared_preferences',
-    );
-
+  group('SharedPreferences', () {
     const Map<String, dynamic> kTestValues = <String, dynamic>{
       'flutter.String': 'hello world',
       'flutter.bool': true,
@@ -30,23 +27,18 @@
       'flutter.List': <String>['baz', 'quox'],
     };
 
-    final List<MethodCall> log = <MethodCall>[];
+    FakeSharedPreferencesStore store;
     SharedPreferences preferences;
 
     setUp(() async {
-      channel.setMockMethodCallHandler((MethodCall methodCall) async {
-        log.add(methodCall);
-        if (methodCall.method == 'getAll') {
-          return kTestValues;
-        }
-        return null;
-      });
+      store = FakeSharedPreferencesStore(kTestValues);
+      SharedPreferencesStorePlatform.instance = store;
       preferences = await SharedPreferences.getInstance();
-      log.clear();
+      store.log.clear();
     });
 
-    tearDown(() {
-      preferences.clear();
+    tearDown(() async {
+      await preferences.clear();
     });
 
     test('reading', () async {
@@ -60,7 +52,7 @@
       expect(preferences.getInt('int'), kTestValues['flutter.int']);
       expect(preferences.getDouble('double'), kTestValues['flutter.double']);
       expect(preferences.getStringList('List'), kTestValues['flutter.List']);
-      expect(log, <Matcher>[]);
+      expect(store.log, <Matcher>[]);
     });
 
     test('writing', () async {
@@ -72,56 +64,60 @@
         preferences.setStringList('List', kTestValues2['flutter.List'])
       ]);
       expect(
-        log,
+        store.log,
         <Matcher>[
-          isMethodCall('setString', arguments: <String, dynamic>{
-            'key': 'flutter.String',
-            'value': kTestValues2['flutter.String']
-          }),
-          isMethodCall('setBool', arguments: <String, dynamic>{
-            'key': 'flutter.bool',
-            'value': kTestValues2['flutter.bool']
-          }),
-          isMethodCall('setInt', arguments: <String, dynamic>{
-            'key': 'flutter.int',
-            'value': kTestValues2['flutter.int']
-          }),
-          isMethodCall('setDouble', arguments: <String, dynamic>{
-            'key': 'flutter.double',
-            'value': kTestValues2['flutter.double']
-          }),
-          isMethodCall('setStringList', arguments: <String, dynamic>{
-            'key': 'flutter.List',
-            'value': kTestValues2['flutter.List']
-          }),
+          isMethodCall('setValue', arguments: <dynamic>[
+            'String',
+            'flutter.String',
+            kTestValues2['flutter.String'],
+          ]),
+          isMethodCall('setValue', arguments: <dynamic>[
+            'Bool',
+            'flutter.bool',
+            kTestValues2['flutter.bool'],
+          ]),
+          isMethodCall('setValue', arguments: <dynamic>[
+            'Int',
+            'flutter.int',
+            kTestValues2['flutter.int'],
+          ]),
+          isMethodCall('setValue', arguments: <dynamic>[
+            'Double',
+            'flutter.double',
+            kTestValues2['flutter.double'],
+          ]),
+          isMethodCall('setValue', arguments: <dynamic>[
+            'StringList',
+            'flutter.List',
+            kTestValues2['flutter.List'],
+          ]),
         ],
       );
-      log.clear();
+      store.log.clear();
 
       expect(preferences.getString('String'), kTestValues2['flutter.String']);
       expect(preferences.getBool('bool'), kTestValues2['flutter.bool']);
       expect(preferences.getInt('int'), kTestValues2['flutter.int']);
       expect(preferences.getDouble('double'), kTestValues2['flutter.double']);
       expect(preferences.getStringList('List'), kTestValues2['flutter.List']);
-      expect(log, equals(<MethodCall>[]));
+      expect(store.log, equals(<MethodCall>[]));
     });
 
     test('removing', () async {
       const String key = 'testKey';
-      preferences
-        ..setString(key, null)
-        ..setBool(key, null)
-        ..setInt(key, null)
-        ..setDouble(key, null)
-        ..setStringList(key, null);
+      await preferences.setString(key, null);
+      await preferences.setBool(key, null);
+      await preferences.setInt(key, null);
+      await preferences.setDouble(key, null);
+      await preferences.setStringList(key, null);
       await preferences.remove(key);
       expect(
-          log,
+          store.log,
           List<Matcher>.filled(
             6,
             isMethodCall(
               'remove',
-              arguments: <String, dynamic>{'key': 'flutter.$key'},
+              arguments: 'flutter.$key',
             ),
             growable: true,
           ));
@@ -132,7 +128,7 @@
 
       expect(false, preferences.containsKey(key));
 
-      preferences.setString(key, 'test');
+      await preferences.setString(key, 'test');
       expect(true, preferences.containsKey(key));
     });
 
@@ -143,7 +139,7 @@
       expect(preferences.getInt('int'), null);
       expect(preferences.getDouble('double'), null);
       expect(preferences.getStringList('List'), null);
-      expect(log, <Matcher>[isMethodCall('clear', arguments: null)]);
+      expect(store.log, <Matcher>[isMethodCall('clear', arguments: null)]);
     });
 
     test('reloading', () async {
@@ -207,3 +203,38 @@
     expect(value, 'foo');
   });
 }
+
+class FakeSharedPreferencesStore implements SharedPreferencesStorePlatform {
+  FakeSharedPreferencesStore(Map<String, Object> data)
+      : backend = InMemorySharedPreferencesStore.withData(data);
+
+  final InMemorySharedPreferencesStore backend;
+  final List<MethodCall> log = <MethodCall>[];
+
+  @override
+  bool get isMock => true;
+
+  @override
+  Future<bool> clear() {
+    log.add(MethodCall('clear'));
+    return backend.clear();
+  }
+
+  @override
+  Future<Map<String, Object>> getAll() {
+    log.add(MethodCall('getAll'));
+    return backend.getAll();
+  }
+
+  @override
+  Future<bool> remove(String key) {
+    log.add(MethodCall('remove', key));
+    return backend.remove(key);
+  }
+
+  @override
+  Future<bool> setValue(String valueType, String key, Object value) {
+    log.add(MethodCall('setValue', <dynamic>[valueType, key, value]));
+    return backend.setValue(valueType, key, value);
+  }
+}
diff --git a/script/build_all_plugins_app.sh b/script/build_all_plugins_app.sh
index 1028ee6..903924f 100755
--- a/script/build_all_plugins_app.sh
+++ b/script/build_all_plugins_app.sh
@@ -15,6 +15,7 @@
   "url_launcher_platform_interface"
   "google_sign_in_platform_interface"
   "video_player_platform_interface"
+  "shared_preferences_platform_interface"
 )
 # Comma-separated string of the list above
 readonly EXCLUDED=$(IFS=, ; echo "${EXCLUDED_PLUGINS_LIST[*]}")