diff --git a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md index 727f2b626d81..c3b4ce33e52b 100644 --- a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 2.0.16 * Updates minimum Flutter version to 3.0. +* Converts `SharedPreferencesAndroid` to Pigeon. ## 2.0.15 diff --git a/packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/Messages.java b/packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/Messages.java new file mode 100644 index 000000000000..ab1b359e27a2 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/Messages.java @@ -0,0 +1,327 @@ +// 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 (v7.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +package io.flutter.plugins.sharedpreferences; + +import android.util.Log; +import androidx.annotation.NonNull; +import io.flutter.plugin.common.BasicMessageChannel; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MessageCodec; +import io.flutter.plugin.common.StandardMessageCodec; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** Generated class from Pigeon. */ +@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"}) +public class Messages { + @NonNull + private static ArrayList wrapError(@NonNull Throwable exception) { + ArrayList errorList = new ArrayList<>(3); + errorList.add(exception.toString()); + errorList.add(exception.getClass().getSimpleName()); + errorList.add( + "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); + return errorList; + } + + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface SharedPreferencesApi { + + /** The codec used by SharedPreferencesApi. */ + static MessageCodec getCodec() { + return new StandardMessageCodec(); + } + + /** + * Sets up an instance of `SharedPreferencesApi` to handle messages through the + * `binaryMessenger`. + */ + static void setup(BinaryMessenger binaryMessenger, SharedPreferencesApi api) { + { + BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue(); + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SharedPreferencesApi.remove", + getCodec(), + taskQueue); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + ArrayList args = (ArrayList) message; + assert args != null; + String keyArg = (String) args.get(0); + if (keyArg == null) { + throw new NullPointerException("keyArg unexpectedly null."); + } + Boolean output = api.remove(keyArg); + wrapped.add(0, output); + } catch (Error | RuntimeException exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue(); + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SharedPreferencesApi.setBool", + getCodec(), + taskQueue); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + ArrayList args = (ArrayList) message; + assert args != null; + String keyArg = (String) args.get(0); + if (keyArg == null) { + throw new NullPointerException("keyArg unexpectedly null."); + } + Boolean valueArg = (Boolean) args.get(1); + if (valueArg == null) { + throw new NullPointerException("valueArg unexpectedly null."); + } + Boolean output = api.setBool(keyArg, valueArg); + wrapped.add(0, output); + } catch (Error | RuntimeException exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue(); + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SharedPreferencesApi.setString", + getCodec(), + taskQueue); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + ArrayList args = (ArrayList) message; + assert args != null; + String keyArg = (String) args.get(0); + if (keyArg == null) { + throw new NullPointerException("keyArg unexpectedly null."); + } + String valueArg = (String) args.get(1); + if (valueArg == null) { + throw new NullPointerException("valueArg unexpectedly null."); + } + Boolean output = api.setString(keyArg, valueArg); + wrapped.add(0, output); + } catch (Error | RuntimeException exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue(); + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SharedPreferencesApi.setInt", + getCodec(), + taskQueue); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + ArrayList args = (ArrayList) message; + assert args != null; + String keyArg = (String) args.get(0); + if (keyArg == null) { + throw new NullPointerException("keyArg unexpectedly null."); + } + Object valueArg = args.get(1); + if (valueArg == null) { + throw new NullPointerException("valueArg unexpectedly null."); + } + Boolean output = api.setInt(keyArg, valueArg); + wrapped.add(0, output); + } catch (Error | RuntimeException exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue(); + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SharedPreferencesApi.setDouble", + getCodec(), + taskQueue); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + ArrayList args = (ArrayList) message; + assert args != null; + String keyArg = (String) args.get(0); + if (keyArg == null) { + throw new NullPointerException("keyArg unexpectedly null."); + } + Double valueArg = (Double) args.get(1); + if (valueArg == null) { + throw new NullPointerException("valueArg unexpectedly null."); + } + Boolean output = api.setDouble(keyArg, valueArg); + wrapped.add(0, output); + } catch (Error | RuntimeException exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue(); + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SharedPreferencesApi.setStringList", + getCodec(), + taskQueue); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + ArrayList args = (ArrayList) message; + assert args != null; + String keyArg = (String) args.get(0); + if (keyArg == null) { + throw new NullPointerException("keyArg unexpectedly null."); + } + List valueArg = (List) args.get(1); + if (valueArg == null) { + throw new NullPointerException("valueArg unexpectedly null."); + } + Boolean output = api.setStringList(keyArg, valueArg); + wrapped.add(0, output); + } catch (Error | RuntimeException exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue(); + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SharedPreferencesApi.clear", + getCodec(), + taskQueue); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + Boolean output = api.clear(); + wrapped.add(0, output); + } catch (Error | RuntimeException exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue(); + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SharedPreferencesApi.getAll", + getCodec(), + taskQueue); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + Map output = api.getAll(); + wrapped.add(0, output); + } catch (Error | RuntimeException exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + + @NonNull + Boolean remove(@NonNull String key); + + @NonNull + Boolean setBool(@NonNull String key, @NonNull Boolean value); + + @NonNull + Boolean setString(@NonNull String key, @NonNull String value); + + @NonNull + Boolean setInt(@NonNull String key, @NonNull Object value); + + @NonNull + Boolean setDouble(@NonNull String key, @NonNull Double value); + + @NonNull + Boolean setStringList(@NonNull String key, @NonNull List value); + + @NonNull + Boolean clear(); + + @NonNull + Map getAll(); + } +} 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 cea3f34b9b96..69dc6d09fdf4 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 @@ -6,11 +6,7 @@ import android.content.Context; import android.content.SharedPreferences; -import android.os.Handler; -import android.os.Looper; import android.util.Base64; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -22,17 +18,12 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -/** - * Implementation of the {@link MethodChannel.MethodCallHandler} for the plugin. It is also - * responsible of managing the {@link android.content.SharedPreferences}. - */ + +/** Helper class to save data to `android.content.SharedPreferences` */ + +// Rename class and file to match it's purpose, preferably SharedPreferencesHelper @SuppressWarnings("unchecked") -class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { +class MethodCallHandlerImpl { private static final String SHARED_PREFERENCES_NAME = "FlutterSharedPreferences"; @@ -43,146 +34,69 @@ class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { private final android.content.SharedPreferences preferences; - private final ExecutorService executor; - private final Handler handler; - - /** - * Constructs a {@link MethodCallHandlerImpl} instance. Creates a {@link - * android.content.SharedPreferences} based on the {@code context}. - */ MethodCallHandlerImpl(Context context) { preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - executor = - new ThreadPoolExecutor(0, 1, 30L, TimeUnit.SECONDS, new LinkedBlockingQueue()); - handler = new Handler(Looper.getMainLooper()); } - @Override - public void onMethodCall(MethodCall call, MethodChannel.Result result) { - String key = call.argument("key"); - try { - switch (call.method) { - case "setBool": - commitAsync(preferences.edit().putBoolean(key, (boolean) call.argument("value")), result); - break; - case "setDouble": - double doubleValue = ((Number) call.argument("value")).doubleValue(); - String doubleValueStr = Double.toString(doubleValue); - commitAsync(preferences.edit().putString(key, DOUBLE_PREFIX + doubleValueStr), result); - break; - case "setInt": - Number number = call.argument("value"); - if (number instanceof BigInteger) { - BigInteger integerValue = (BigInteger) number; - commitAsync( - preferences - .edit() - .putString( - key, BIG_INTEGER_PREFIX + integerValue.toString(Character.MAX_RADIX)), - result); - } else { - commitAsync(preferences.edit().putLong(key, number.longValue()), result); - } - break; - case "setString": - String value = (String) call.argument("value"); - if (value.startsWith(LIST_IDENTIFIER) - || value.startsWith(BIG_INTEGER_PREFIX) - || value.startsWith(DOUBLE_PREFIX)) { - result.error( - "StorageError", - "This string cannot be stored as it clashes with special identifier prefixes.", - null); - return; - } - commitAsync(preferences.edit().putString(key, value), result); - break; - case "setStringList": - List list = call.argument("value"); - commitAsync( - preferences.edit().putString(key, LIST_IDENTIFIER + encodeList(list)), result); - break; - case "commit": - // We've been committing the whole time. - result.success(true); - break; - case "getAll": - result.success(getAllPrefs()); - return; - case "remove": - commitAsync(preferences.edit().remove(key), result); - break; - case "clear": - Set keySet = getAllPrefs().keySet(); - SharedPreferences.Editor clearEditor = preferences.edit(); - for (String keyToDelete : keySet) { - clearEditor.remove(keyToDelete); - } - commitAsync(clearEditor, result); - break; - default: - result.notImplemented(); - break; - } - } catch (IOException e) { - result.error("IOException encountered", call.method, e); + public Boolean setBool(String key, Boolean value) { + return preferences.edit().putBoolean(key, value).commit(); + } + + public Boolean setString(String key, String value) { + if (value.startsWith(LIST_IDENTIFIER) + || value.startsWith(BIG_INTEGER_PREFIX) + || value.startsWith(DOUBLE_PREFIX)) { + throw new RuntimeException( + "StorageError: This string cannot be stored as it clashes with special identifier prefixes"); } + return preferences.edit().putString(key, value).commit(); } - public void teardown() { - handler.removeCallbacksAndMessages(null); - executor.shutdown(); + public Boolean setInt(String key, Object value) { + Number number = (Number) value; + if (number instanceof BigInteger) { + BigInteger integerValue = (BigInteger) number; + return preferences + .edit() + .putString(key, BIG_INTEGER_PREFIX + integerValue.toString(Character.MAX_RADIX)) + .commit(); + } else { + return preferences.edit().putLong(key, number.longValue()).commit(); + } } - private void commitAsync( - final SharedPreferences.Editor editor, final MethodChannel.Result result) { - executor.execute( - new Runnable() { - @Override - public void run() { - final boolean response = editor.commit(); - handler.post( - new Runnable() { - @Override - public void run() { - result.success(response); - } - }); - } - }); + public Boolean setDouble(String key, Double value) { + String doubleValueStr = Double.toString(value); + return preferences.edit().putString(key, DOUBLE_PREFIX + doubleValueStr).commit(); } - private List decodeList(String encodedList) throws IOException { - ObjectInputStream stream = null; - try { - stream = new ObjectInputStream(new ByteArrayInputStream(Base64.decode(encodedList, 0))); - return (List) stream.readObject(); - } catch (ClassNotFoundException e) { - throw new IOException(e); - } finally { - if (stream != null) { - stream.close(); - } - } + public Boolean setStringList(String key, List value) throws RuntimeException { + Boolean success = + preferences.edit().putString(key, LIST_IDENTIFIER + encodeList(value)).commit(); + return success; } - private String encodeList(List list) throws IOException { - ObjectOutputStream stream = null; - try { - ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); - stream = new ObjectOutputStream(byteStream); - stream.writeObject(list); - stream.flush(); - return Base64.encodeToString(byteStream.toByteArray(), 0); - } finally { - if (stream != null) { - stream.close(); - } + public Map getAll() throws RuntimeException { + Map data = getAllPrefs(); + return data; + } + + public Boolean remove(String key) { + return preferences.edit().remove(key).commit(); + } + + public Boolean clear() throws RuntimeException { + Set keySet = getAllPrefs().keySet(); + SharedPreferences.Editor clearEditor = preferences.edit(); + for (String keyToDelete : keySet) { + clearEditor.remove(keyToDelete); } + return clearEditor.commit(); } // Filter preferences to only those set by the flutter app. - private Map getAllPrefs() throws IOException { + @SuppressWarnings("unchecked") + private Map getAllPrefs() throws RuntimeException { Map allPrefs = preferences.getAll(); Map filteredPrefs = new HashMap<>(); for (String key : allPrefs.keySet()) { @@ -201,24 +115,59 @@ private Map getAllPrefs() throws IOException { } } else if (value instanceof Set) { // This only happens for previous usage of setStringSet. The app expects a list. - List listValue = new ArrayList<>((Set) value); + List listValue = new ArrayList((Set) value); // Let's migrate the value too while we are at it. - boolean success = - preferences - .edit() - .remove(key) - .putString(key, LIST_IDENTIFIER + encodeList(listValue)) - .commit(); - if (!success) { + try { + preferences + .edit() + .remove(key) + .putString(key, LIST_IDENTIFIER + encodeList(listValue)) + .commit(); + } catch (RuntimeException e) { // If we are unable to migrate the existing preferences, it means we potentially lost them. // In this case, an error from getAllPrefs() is appropriate since it will alert the app during plugin initialization. - throw new IOException("Could not migrate set to list"); + throw e; } value = listValue; } filteredPrefs.put(key, value); } } + return filteredPrefs; } + + @SuppressWarnings("unchecked") + private List decodeList(String encodedList) throws RuntimeException { + ObjectInputStream stream = null; + try { + stream = new ObjectInputStream(new ByteArrayInputStream(Base64.decode(encodedList, 0))); + List data = (List) stream.readObject(); + if (stream != null) { + stream.close(); + } + + return data; + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + private String encodeList(List list) throws RuntimeException { + ObjectOutputStream stream = null; + try { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + stream = new ObjectOutputStream(byteStream); + stream.writeObject(list); + stream.flush(); + String data = Base64.encodeToString(byteStream.toByteArray(), 0); + if (stream != null) { + stream.close(); + } + + return data; + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.java b/packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.java index 9545fe95c54b..dc1a0139f76c 100644 --- a/packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.java +++ b/packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.java @@ -4,43 +4,85 @@ package io.flutter.plugins.sharedpreferences; +import android.annotation.SuppressLint; import android.content.Context; +import android.util.Log; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugins.sharedpreferences.Messages.SharedPreferencesApi; +import java.util.List; +import java.util.Map; /** SharedPreferencesPlugin */ -public class SharedPreferencesPlugin implements FlutterPlugin { - private static final String CHANNEL_NAME = "plugins.flutter.io/shared_preferences_android"; - private MethodChannel channel; - private MethodCallHandlerImpl handler; +public class SharedPreferencesPlugin implements FlutterPlugin, SharedPreferencesApi { + final String TAG = "SharedPreferencesPlugin.java"; + + // SharedPreferences Helper Object, exposes SharedPreferences methods + private MethodCallHandlerImpl preferences; @SuppressWarnings("deprecation") public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { final SharedPreferencesPlugin plugin = new SharedPreferencesPlugin(); - plugin.setupChannel(registrar.messenger(), registrar.context()); + plugin.setup(registrar.messenger(), registrar.context()); + } + + @SuppressLint("LongLogTag") + private void setup(BinaryMessenger messenger, Context context) { + preferences = new MethodCallHandlerImpl(context); + try { + SharedPreferencesApi.setup(messenger, this); + } catch (Exception ex) { + Log.e(TAG, "Received exception while setting up SharedPreferencesPlugin", ex); + } } @Override public void onAttachedToEngine(FlutterPlugin.FlutterPluginBinding binding) { - setupChannel(binding.getBinaryMessenger(), binding.getApplicationContext()); + setup(binding.getBinaryMessenger(), binding.getApplicationContext()); } @Override public void onDetachedFromEngine(FlutterPlugin.FlutterPluginBinding binding) { - teardownChannel(); + SharedPreferencesApi.setup(binding.getBinaryMessenger(), null); + } + + @Override + public Boolean setBool(String key, Boolean value) { + return preferences.setBool(key, value); } - private void setupChannel(BinaryMessenger messenger, Context context) { - channel = new MethodChannel(messenger, CHANNEL_NAME); - handler = new MethodCallHandlerImpl(context); - channel.setMethodCallHandler(handler); + @Override + public Boolean setString(String key, String value) { + return preferences.setString(key, value); + } + + @Override + public Boolean setInt(String key, Object value) { + return preferences.setInt(key, value); } - private void teardownChannel() { - handler.teardown(); - handler = null; - channel.setMethodCallHandler(null); - channel = null; + @Override + public Boolean setDouble(String key, Double value) { + return preferences.setDouble(key, value); + } + + @Override + public Boolean remove(String key) { + return preferences.remove(key); + } + + @Override + public Boolean setStringList(String key, List value) throws RuntimeException { + return preferences.setStringList(key, value); + } + + @Override + public Map getAll() throws RuntimeException { + return preferences.getAll(); + } + + @Override + public Boolean clear() throws RuntimeException { + return preferences.clear(); } } diff --git a/packages/shared_preferences/shared_preferences_android/android/src/test/java/io/flutter/plugins/sharedpreferences/SharedPreferencesTest.java b/packages/shared_preferences/shared_preferences_android/android/src/test/java/io/flutter/plugins/sharedpreferences/SharedPreferencesTest.java index 13d0ff8b40c1..ed61902274ed 100644 --- a/packages/shared_preferences/shared_preferences_android/android/src/test/java/io/flutter/plugins/sharedpreferences/SharedPreferencesTest.java +++ b/packages/shared_preferences/shared_preferences_android/android/src/test/java/io/flutter/plugins/sharedpreferences/SharedPreferencesTest.java @@ -4,12 +4,131 @@ package io.flutter.plugins.sharedpreferences; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import androidx.annotation.NonNull; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.junit.Test; +// Class Mocking SharedPreferencesAPI +class MockAPI implements Messages.SharedPreferencesApi { + + Map items = new HashMap(); + + @Override + public Boolean remove(@NonNull String key) { + items.remove(key); + return true; + } + + @Override + public Boolean setBool(@NonNull String key, @NonNull Boolean value) { + items.put(key, value); + return true; + } + + @Override + public Boolean setString(@NonNull String key, @NonNull String value) { + items.put(key, value); + return true; + } + + @Override + public Boolean setInt(@NonNull String key, @NonNull Object value) { + items.put(key, value); + return true; + } + + @Override + public Boolean setDouble(@NonNull String key, @NonNull Double value) { + items.put(key, value); + return true; + } + + @Override + public Boolean setStringList(@NonNull String key, @NonNull List value) { + items.put(key, value); + return true; + } + + @Override + public Boolean clear() { + items.clear(); + return true; + } + + @Override + public Map getAll() { + return items; + } +} + public class SharedPreferencesTest { - // This is only a placeholder test and doesn't actually initialize the plugin. + + MockAPI api = new MockAPI(); + + Map data = + new HashMap() { + { + put("Language", "Java"); + put("Counter", 0); + put("Pie", 3.14); + put("Names", Arrays.asList("Flutter", "Dart")); + put("NewToFlutter", false); + } + }; + @Test public void initPluginDoesNotThrow() { final SharedPreferencesPlugin plugin = new SharedPreferencesPlugin(); } + + @Test + public void testAddValues() { + assertEquals(api.getAll().size(), 0); + + addData(api); + + assertEquals(api.getAll().size(), 5); + assertEquals(api.getAll().get("Language"), "Java"); + assertEquals(api.getAll().get("Counter"), 0); + assertEquals(api.getAll().get("Pie"), 3.14); + assertEquals(api.getAll().get("Names"), Arrays.asList("Flutter", "Dart")); + assertEquals(api.getAll().get("NewToFlutter"), false); + } + + @Test + public void testGetAllData() { + assertEquals(api.getAll(), new HashMap()); + addData(api); + + assertEquals(api.getAll(), data); + } + + @Test + public void testClear() { + addData(api); + assertEquals(api.getAll().size(), 5); + api.clear(); + assertEquals(api.getAll().size(), 0); + } + + @Test + public void testRemove() { + api.setBool("isJava", true); + api.remove("isJava"); + assertFalse(api.getAll().containsKey("isJava")); + } + + public void addData(MockAPI api) { + api.setString("Language", "Java"); + api.setInt("Counter", 0); + api.setDouble("Pie", 3.14); + api.setStringList("Names", Arrays.asList("Flutter", "Dart")); + api.setBool("NewToFlutter", false); + } } diff --git a/packages/shared_preferences/shared_preferences_android/lib/messages.g.dart b/packages/shared_preferences/shared_preferences_android/lib/messages.g.dart new file mode 100644 index 000000000000..592304f9f8d3 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_android/lib/messages.g.dart @@ -0,0 +1,238 @@ +// 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 (v7.0.2), 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; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +class SharedPreferencesApi { + /// Constructor for [SharedPreferencesApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + SharedPreferencesApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + Future remove(String arg_key) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SharedPreferencesApi.remove', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_key]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + Future setBool(String arg_key, bool arg_value) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SharedPreferencesApi.setBool', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_key, arg_value]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + Future setString(String arg_key, String arg_value) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SharedPreferencesApi.setString', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_key, arg_value]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + Future setInt(String arg_key, Object arg_value) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SharedPreferencesApi.setInt', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_key, arg_value]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + Future setDouble(String arg_key, double arg_value) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SharedPreferencesApi.setDouble', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_key, arg_value]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + Future setStringList(String arg_key, List arg_value) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SharedPreferencesApi.setStringList', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_key, arg_value]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + Future clear() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SharedPreferencesApi.clear', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + Future> getAll() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SharedPreferencesApi.getAll', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as Map?)! + .cast(); + } + } +} 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 da5147d32da2..e7eee65a201e 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 @@ -7,13 +7,14 @@ import 'dart:async'; import 'package:flutter/services.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; -const MethodChannel _kChannel = - MethodChannel('plugins.flutter.io/shared_preferences_android'); +import 'messages.g.dart'; /// The Android implementation of [SharedPreferencesStorePlatform]. /// /// This class implements the `package:shared_preferences` functionality for Android. class SharedPreferencesAndroid extends SharedPreferencesStorePlatform { + final SharedPreferencesApi _api = SharedPreferencesApi(); + /// Registers this class as the default instance of [SharedPreferencesStorePlatform]. static void registerWith() { SharedPreferencesStorePlatform.instance = SharedPreferencesAndroid(); @@ -21,33 +22,48 @@ class SharedPreferencesAndroid extends SharedPreferencesStorePlatform { @override Future remove(String key) async { - return (await _kChannel.invokeMethod( - 'remove', - {'key': key}, - ))!; + return _api.remove(key); } @override Future setValue(String valueType, String key, Object value) async { - return (await _kChannel.invokeMethod( - 'set$valueType', - {'key': key, 'value': value}, - ))!; + return await _handleSetValue(valueType, key, value) ?? false; } @override Future clear() async { - return (await _kChannel.invokeMethod('clear'))!; + return _api.clear(); } @override Future> getAll() async { - final Map? preferences = - await _kChannel.invokeMapMethod('getAll'); - + final Map data = await _api.getAll(); + final Map preferences = data.cast(); if (preferences == null) { return {}; } + return preferences; } + + // Call the function according to the type of value provided + Future _handleSetValue( + String dataType, String key, Object value) async { + switch (dataType) { + case 'String': + return _api.setString(key, value as String); + case 'Bool': + return _api.setBool(key, value as bool); + case 'Int': + return _api.setInt(key, value as int); + case 'Double': + return _api.setDouble(key, value as double); + case 'StringList': + return _api.setStringList(key, value as List); + } + + throw PlatformException( + code: 'InvalidOperation', + message: '"$dataType" is not a supported type.'); + } } diff --git a/packages/shared_preferences/shared_preferences_android/pigeons/copyright.txt b/packages/shared_preferences/shared_preferences_android/pigeons/copyright.txt new file mode 100644 index 000000000000..1236b63caf3a --- /dev/null +++ b/packages/shared_preferences/shared_preferences_android/pigeons/copyright.txt @@ -0,0 +1,3 @@ +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. diff --git a/packages/shared_preferences/shared_preferences_android/pigeons/messages.dart b/packages/shared_preferences/shared_preferences_android/pigeons/messages.dart new file mode 100644 index 000000000000..f3b6d250fde4 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_android/pigeons/messages.dart @@ -0,0 +1,35 @@ +// 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 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + input: 'pigeons/messages.dart', + javaOut: + 'android/src/main/java/io/flutter/plugins/sharedpreferences/Messages.java', + javaOptions: JavaOptions( + className: 'Messages', package: 'io.flutter.plugins.sharedpreferences'), + dartOut: 'lib/messages.g.dart', + dartTestOut: 'test/messages_test.g.dart', + copyrightHeader: 'pigeons/copyright.txt', +)) +@HostApi(dartHostTestHandler: 'TestSharedPreferencesApi') +abstract class SharedPreferencesApi { + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + bool remove(String key); + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + bool setBool(String key, bool value); + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + bool setString(String key, String value); + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + bool setInt(String key, Object value); + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + bool setDouble(String key, double value); + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + bool setStringList(String key, List value); + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + bool clear(); + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + Map getAll(); +} diff --git a/packages/shared_preferences/shared_preferences_android/pubspec.yaml b/packages/shared_preferences/shared_preferences_android/pubspec.yaml index d968dcbce55b..0c69e9d9755f 100644 --- a/packages/shared_preferences/shared_preferences_android/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_android/pubspec.yaml @@ -2,7 +2,7 @@ name: shared_preferences_android description: Android implementation of the shared_preferences plugin repository: https://github.com/flutter/plugins/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.15 +version: 2.0.16 environment: sdk: ">=2.14.0 <3.0.0" @@ -25,3 +25,4 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + pigeon: ^7.0.2 diff --git a/packages/shared_preferences/shared_preferences_android/test/messages_test.g.dart b/packages/shared_preferences/shared_preferences_android/test/messages_test.g.dart new file mode 100644 index 000000000000..9a300592503b --- /dev/null +++ b/packages/shared_preferences/shared_preferences_android/test/messages_test.g.dart @@ -0,0 +1,196 @@ +// 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 (v7.0.2), 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 +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:shared_preferences_android/messages.g.dart'; + +abstract class TestSharedPreferencesApi { + static const MessageCodec codec = StandardMessageCodec(); + + bool remove(String key); + + bool setBool(String key, bool value); + + bool setString(String key, String value); + + bool setInt(String key, Object value); + + bool setDouble(String key, double value); + + bool setStringList(String key, List value); + + bool clear(); + + Map getAll(); + + static void setup(TestSharedPreferencesApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SharedPreferencesApi.remove', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.remove was null.'); + final List args = (message as List?)!; + final String? arg_key = (args[0] as String?); + assert(arg_key != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.remove was null, expected non-null String.'); + final bool output = api.remove(arg_key!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SharedPreferencesApi.setBool', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.setBool was null.'); + final List args = (message as List?)!; + final String? arg_key = (args[0] as String?); + assert(arg_key != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.setBool was null, expected non-null String.'); + final bool? arg_value = (args[1] as bool?); + assert(arg_value != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.setBool was null, expected non-null bool.'); + final bool output = api.setBool(arg_key!, arg_value!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SharedPreferencesApi.setString', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.setString was null.'); + final List args = (message as List?)!; + final String? arg_key = (args[0] as String?); + assert(arg_key != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.setString was null, expected non-null String.'); + final String? arg_value = (args[1] as String?); + assert(arg_value != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.setString was null, expected non-null String.'); + final bool output = api.setString(arg_key!, arg_value!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SharedPreferencesApi.setInt', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.setInt was null.'); + final List args = (message as List?)!; + final String? arg_key = (args[0] as String?); + assert(arg_key != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.setInt was null, expected non-null String.'); + final Object? arg_value = (args[1] as Object?); + assert(arg_value != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.setInt was null, expected non-null Object.'); + final bool output = api.setInt(arg_key!, arg_value!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SharedPreferencesApi.setDouble', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.setDouble was null.'); + final List args = (message as List?)!; + final String? arg_key = (args[0] as String?); + assert(arg_key != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.setDouble was null, expected non-null String.'); + final double? arg_value = (args[1] as double?); + assert(arg_value != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.setDouble was null, expected non-null double.'); + final bool output = api.setDouble(arg_key!, arg_value!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SharedPreferencesApi.setStringList', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.setStringList was null.'); + final List args = (message as List?)!; + final String? arg_key = (args[0] as String?); + assert(arg_key != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.setStringList was null, expected non-null String.'); + final List? arg_value = + (args[1] as List?)?.cast(); + assert(arg_value != null, + 'Argument for dev.flutter.pigeon.SharedPreferencesApi.setStringList was null, expected non-null List.'); + final bool output = api.setStringList(arg_key!, arg_value!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SharedPreferencesApi.clear', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + // ignore message + final bool output = api.clear(); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SharedPreferencesApi.getAll', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + // ignore message + final Map output = api.getAll(); + return [output]; + }); + } + } + } +} 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 fb6764893651..c260c2817a48 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 @@ -8,63 +8,68 @@ import 'package:shared_preferences_android/shared_preferences_android.dart'; import 'package:shared_preferences_platform_interface/method_channel_shared_preferences.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; +import '../test/messages_test.g.dart'; + +// Mock Implementation of the SharedPreferencesApi +class _MockAPI extends TestSharedPreferencesApi { + final Map items = {}; + + @override + bool clear() { + items.clear(); + return true; + } + + @override + Map getAll() { + return items; + } + + @override + bool remove(String key) { + items.remove(key); + return true; + } + + @override + bool setBool(String key, bool value) { + items[key] = value; + return true; + } + + @override + bool setDouble(String key, double value) { + items[key] = value; + return true; + } + + @override + bool setInt(String key, Object value) { + items[key] = value; + return true; + } + + @override + bool setString(String key, String value) { + items[key] = value; + return true; + } + + @override + bool setStringList(String key, List value) { + items[key] = value; + return true; + } +} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); + late _MockAPI api; group(MethodChannelSharedPreferencesStore, () { - const MethodChannel channel = MethodChannel( - 'plugins.flutter.io/shared_preferences_android', - ); - - const Map kTestValues = { - 'flutter.String': 'hello world', - 'flutter.Bool': true, - 'flutter.Int': 42, - 'flutter.Double': 3.14159, - 'flutter.StringList': ['foo', 'bar'], - }; - // Create a dummy in-memory implementation to back the mocked method channel - // API to simplify validation of the expected calls. - late InMemorySharedPreferencesStore testData; - - final List log = []; - late SharedPreferencesStorePlatform store; - setUp(() async { - testData = InMemorySharedPreferencesStore.empty(); - - Map getArgumentDictionary(MethodCall call) { - return (call.arguments as Map) - .cast(); - } - - channel.setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - if (methodCall.method == 'getAll') { - return testData.getAll(); - } - if (methodCall.method == 'remove') { - final Map arguments = - getArgumentDictionary(methodCall); - final String key = arguments['key']! as String; - return testData.remove(key); - } - if (methodCall.method == 'clear') { - return testData.clear(); - } - final RegExp setterRegExp = RegExp(r'set(.*)'); - final Match? match = setterRegExp.matchAsPrefix(methodCall.method); - if (match?.groupCount == 1) { - final String valueType = match!.group(1)!; - final Map arguments = - getArgumentDictionary(methodCall); - final String key = arguments['key']! as String; - final Object value = arguments['value']!; - return testData.setValue(valueType, key, value); - } - fail('Unexpected method call: ${methodCall.method}'); - }); - log.clear(); + api = _MockAPI(); + TestSharedPreferencesApi.setup(api); }); test('registered instance', () { @@ -72,55 +77,58 @@ void main() { expect(SharedPreferencesStorePlatform.instance, isA()); }); + test('remove', () async { + final SharedPreferencesAndroid plugin = SharedPreferencesAndroid(); + api.items['flutter.hi'] = 'world'; + expect(await plugin.remove('flutter.hi'), isTrue); + expect(api.items.containsKey('flutter.hi'), isFalse); + }); - test('getAll', () async { - store = SharedPreferencesAndroid(); - testData = InMemorySharedPreferencesStore.withData(kTestValues); - expect(await store.getAll(), kTestValues); - expect(log.single.method, 'getAll'); + test('clear', () async { + final SharedPreferencesAndroid plugin = SharedPreferencesAndroid(); + api.items['flutter.hi'] = 'world'; + expect(await plugin.clear(), isTrue); + expect(api.items.containsKey('flutter.hi'), isFalse); }); - test('remove', () async { - store = SharedPreferencesAndroid(); - testData = InMemorySharedPreferencesStore.withData(kTestValues); - expect(await store.remove('flutter.String'), true); - expect(await store.remove('flutter.Bool'), true); - expect(await store.remove('flutter.Int'), true); - expect(await store.remove('flutter.Double'), true); - expect(await testData.getAll(), { - 'flutter.StringList': ['foo', 'bar'], - }); - - expect(log, hasLength(4)); - for (final MethodCall call in log) { - expect(call.method, 'remove'); - } + test('getAll', () async { + final SharedPreferencesAndroid plugin = SharedPreferencesAndroid(); + 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'] = ['hello', 'world']; + final Map 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']); }); test('setValue', () async { - store = SharedPreferencesAndroid(); - expect(await testData.getAll(), isEmpty); - for (final String key in kTestValues.keys) { - final Object value = kTestValues[key]!; - expect(await store.setValue(key.split('.').last, key, value), true); - } - expect(await testData.getAll(), kTestValues); - - expect(log, hasLength(5)); - expect(log[0].method, 'setString'); - expect(log[1].method, 'setBool'); - expect(log[2].method, 'setInt'); - expect(log[3].method, 'setDouble'); - expect(log[4].method, 'setStringList'); + final SharedPreferencesAndroid plugin = SharedPreferencesAndroid(); + expect(await plugin.setValue('Bool', 'flutter.Bool', true), isTrue); + expect(api.items['flutter.Bool'], true); + expect(await plugin.setValue('Double', 'flutter.Double', 1.5), isTrue); + expect(api.items['flutter.Double'], 1.5); + expect(await plugin.setValue('Int', 'flutter.Int', 12), isTrue); + expect(api.items['flutter.Int'], 12); + expect(await plugin.setValue('String', 'flutter.String', 'hi'), isTrue); + expect(api.items['flutter.String'], 'hi'); + expect( + await plugin + .setValue('StringList', 'flutter.StringList', ['hi']), + isTrue); + expect(api.items['flutter.StringList'], ['hi']); }); - - test('clear', () async { - store = SharedPreferencesAndroid(); - testData = InMemorySharedPreferencesStore.withData(kTestValues); - expect(await testData.getAll(), isNotEmpty); - expect(await store.clear(), true); - expect(await testData.getAll(), isEmpty); - expect(log.single.method, 'clear'); + test('setValue with unsupported type', () { + final SharedPreferencesAndroid plugin = SharedPreferencesAndroid(); + expect(() async { + await plugin.setValue('Map', 'flutter.key', {}); + }, throwsA(isA())); }); }); }