diff --git a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md index aa30f0cd186..1691a73764c 100644 --- a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.3.0 + +* Adds new `SharedPreferencesAsyncAndroid` API. + ## 2.2.4 * Updates lint checks to ignore NewerVersionAvailable. diff --git a/packages/shared_preferences/shared_preferences_android/android/build.gradle b/packages/shared_preferences/shared_preferences_android/android/build.gradle index 99cf82462b2..11095a1352f 100644 --- a/packages/shared_preferences/shared_preferences_android/android/build.gradle +++ b/packages/shared_preferences/shared_preferences_android/android/build.gradle @@ -2,6 +2,7 @@ group 'io.flutter.plugins.sharedpreferences' version '1.0-SNAPSHOT' buildscript { + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() @@ -9,6 +10,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.2.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -28,6 +30,7 @@ allprojects { } apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' android { // Conditional for compatibility with AGP <4.2. @@ -41,6 +44,14 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + defaultConfig { minSdkVersion 19 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -51,8 +62,14 @@ android { disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency', 'NewerVersionAvailable' } dependencies { + implementation 'androidx.datastore:datastore:1.0.0' + implementation 'androidx.datastore:datastore-preferences:1.0.0' testImplementation 'junit:junit:4.13.2' + testImplementation 'androidx.test:core-ktx:1.5.0' + testImplementation 'androidx.test.ext:junit-ktx:1.1.5' + testImplementation 'org.robolectric:robolectric:4.12.1' testImplementation 'org.mockito:mockito-inline:5.2.0' + testImplementation 'io.mockk:mockk:1.13.12' } 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/LegacySharedPreferencesPlugin.java similarity index 95% rename from packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.java rename to packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/LegacySharedPreferencesPlugin.java index 6bfa4e285a6..7d4dfd5d7e4 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/LegacySharedPreferencesPlugin.java @@ -27,8 +27,8 @@ import java.util.Map; import java.util.Set; -/** SharedPreferencesPlugin */ -public class SharedPreferencesPlugin implements FlutterPlugin, SharedPreferencesApi { +/** LegacySharedPreferencesPlugin */ +public class LegacySharedPreferencesPlugin implements FlutterPlugin, SharedPreferencesApi { private static final String TAG = "SharedPreferencesPlugin"; private static final String SHARED_PREFERENCES_NAME = "FlutterSharedPreferences"; private static final String LIST_IDENTIFIER = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu"; @@ -38,19 +38,19 @@ public class SharedPreferencesPlugin implements FlutterPlugin, SharedPreferences private SharedPreferences preferences; private SharedPreferencesListEncoder listEncoder; - public SharedPreferencesPlugin() { + public LegacySharedPreferencesPlugin() { this(new ListEncoder()); } @VisibleForTesting - SharedPreferencesPlugin(@NonNull SharedPreferencesListEncoder listEncoder) { + LegacySharedPreferencesPlugin(@NonNull SharedPreferencesListEncoder listEncoder) { this.listEncoder = listEncoder; } private void setUp(@NonNull BinaryMessenger messenger, @NonNull Context context) { preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); try { - SharedPreferencesApi.setup(messenger, this); + SharedPreferencesApi.setUp(messenger, this); } catch (Exception ex) { Log.e(TAG, "Received exception while setting up SharedPreferencesPlugin", ex); } @@ -63,7 +63,7 @@ public void onAttachedToEngine(@NonNull FlutterPlugin.FlutterPluginBinding bindi @Override public void onDetachedFromEngine(@NonNull FlutterPlugin.FlutterPluginBinding binding) { - SharedPreferencesApi.setup(binding.getBinaryMessenger(), null); + SharedPreferencesApi.setUp(binding.getBinaryMessenger(), null); } @Override 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 index 26fb7958fa9..4041ad9aa6f 100644 --- 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 @@ -1,7 +1,7 @@ // 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 (v9.2.5), do not edit directly. +// Autogenerated from Pigeon (v16.0.4), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.sharedpreferences; @@ -88,14 +88,14 @@ public interface SharedPreferencesApi { * Sets up an instance of `SharedPreferencesApi` to handle messages through the * `binaryMessenger`. */ - static void setup( + static void setUp( @NonNull BinaryMessenger binaryMessenger, @Nullable SharedPreferencesApi api) { { BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue(); BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.SharedPreferencesApi.remove", + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesApi.remove", getCodec(), taskQueue); if (api != null) { @@ -122,7 +122,7 @@ static void setup( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.SharedPreferencesApi.setBool", + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesApi.setBool", getCodec(), taskQueue); if (api != null) { @@ -150,7 +150,7 @@ static void setup( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.SharedPreferencesApi.setString", + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesApi.setString", getCodec(), taskQueue); if (api != null) { @@ -178,7 +178,7 @@ static void setup( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.SharedPreferencesApi.setInt", + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesApi.setInt", getCodec(), taskQueue); if (api != null) { @@ -207,7 +207,7 @@ static void setup( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.SharedPreferencesApi.setDouble", + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesApi.setDouble", getCodec(), taskQueue); if (api != null) { @@ -235,7 +235,7 @@ static void setup( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.SharedPreferencesApi.setStringList", + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesApi.setStringList", getCodec(), taskQueue); if (api != null) { @@ -263,7 +263,7 @@ static void setup( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.SharedPreferencesApi.clear", + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesApi.clear", getCodec(), taskQueue); if (api != null) { @@ -291,7 +291,7 @@ static void setup( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.SharedPreferencesApi.getAll", + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesApi.getAll", getCodec(), taskQueue); if (api != null) { diff --git a/packages/shared_preferences/shared_preferences_android/android/src/main/kotlin/io/flutter/plugins/sharedpreferences/MessagesAsync.g.kt b/packages/shared_preferences/shared_preferences_android/android/src/main/kotlin/io/flutter/plugins/sharedpreferences/MessagesAsync.g.kt new file mode 100644 index 00000000000..159253f4280 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_android/android/src/main/kotlin/io/flutter/plugins/sharedpreferences/MessagesAsync.g.kt @@ -0,0 +1,453 @@ +// 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 (v16.0.5), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +package io.flutter.plugins.sharedpreferences + +import android.util.Log +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.io.ByteArrayOutputStream +import java.nio.ByteBuffer + +private fun wrapResult(result: Any?): List { + return listOf(result) +} + +private fun wrapError(exception: Throwable): List { + if (exception is SharedPreferencesError) { + return listOf(exception.code, exception.message, exception.details) + } else { + return listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)) + } +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class SharedPreferencesError( + val code: String, + override val message: String? = null, + val details: Any? = null +) : Throwable() + +/** Generated class from Pigeon that represents data sent in messages. */ +data class SharedPreferencesPigeonOptions(val fileKey: String? = null) { + + companion object { + @Suppress("UNCHECKED_CAST") + fun fromList(list: List): SharedPreferencesPigeonOptions { + val fileKey = list[0] as String? + return SharedPreferencesPigeonOptions(fileKey) + } + } + + fun toList(): List { + return listOf( + fileKey, + ) + } +} + +@Suppress("UNCHECKED_CAST") +private object SharedPreferencesAsyncApiCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 128.toByte() -> { + return (readValue(buffer) as? List)?.let { + SharedPreferencesPigeonOptions.fromList(it) + } + } + else -> super.readValueOfType(type, buffer) + } + } + + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is SharedPreferencesPigeonOptions -> { + stream.write(128) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface SharedPreferencesAsyncApi { + /** Adds property to shared preferences data set of type bool. */ + fun setBool(key: String, value: Boolean, options: SharedPreferencesPigeonOptions) + /** Adds property to shared preferences data set of type String. */ + fun setString(key: String, value: String, options: SharedPreferencesPigeonOptions) + /** Adds property to shared preferences data set of type int. */ + fun setInt(key: String, value: Long, options: SharedPreferencesPigeonOptions) + /** Adds property to shared preferences data set of type double. */ + fun setDouble(key: String, value: Double, options: SharedPreferencesPigeonOptions) + /** Adds property to shared preferences data set of type List. */ + fun setStringList(key: String, value: List, options: SharedPreferencesPigeonOptions) + /** Gets individual String value stored with [key], if any. */ + fun getString(key: String, options: SharedPreferencesPigeonOptions): String? + /** Gets individual void value stored with [key], if any. */ + fun getBool(key: String, options: SharedPreferencesPigeonOptions): Boolean? + /** Gets individual double value stored with [key], if any. */ + fun getDouble(key: String, options: SharedPreferencesPigeonOptions): Double? + /** Gets individual int value stored with [key], if any. */ + fun getInt(key: String, options: SharedPreferencesPigeonOptions): Long? + /** Gets individual List value stored with [key], if any. */ + fun getStringList(key: String, options: SharedPreferencesPigeonOptions): List? + /** Removes all properties from shared preferences data set with matching prefix. */ + fun clear(allowList: List?, options: SharedPreferencesPigeonOptions) + /** Gets all properties from shared preferences data set with matching prefix. */ + fun getAll(allowList: List?, options: SharedPreferencesPigeonOptions): Map + /** Gets all properties from shared preferences data set with matching prefix. */ + fun getKeys(allowList: List?, options: SharedPreferencesPigeonOptions): List + + companion object { + /** The codec used by SharedPreferencesAsyncApi. */ + val codec: MessageCodec by lazy { SharedPreferencesAsyncApiCodec } + /** + * Sets up an instance of `SharedPreferencesAsyncApi` to handle messages through the + * `binaryMessenger`. + */ + @Suppress("UNCHECKED_CAST") + fun setUp(binaryMessenger: BinaryMessenger, api: SharedPreferencesAsyncApi?) { + run { + val taskQueue = binaryMessenger.makeBackgroundTaskQueue() + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.setBool", + codec, + taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val keyArg = args[0] as String + val valueArg = args[1] as Boolean + val optionsArg = args[2] as SharedPreferencesPigeonOptions + var wrapped: List + try { + api.setBool(keyArg, valueArg, optionsArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val taskQueue = binaryMessenger.makeBackgroundTaskQueue() + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.setString", + codec, + taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val keyArg = args[0] as String + val valueArg = args[1] as String + val optionsArg = args[2] as SharedPreferencesPigeonOptions + var wrapped: List + try { + api.setString(keyArg, valueArg, optionsArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val taskQueue = binaryMessenger.makeBackgroundTaskQueue() + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.setInt", + codec, + taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val keyArg = args[0] as String + val valueArg = args[1].let { if (it is Int) it.toLong() else it as Long } + val optionsArg = args[2] as SharedPreferencesPigeonOptions + var wrapped: List + try { + api.setInt(keyArg, valueArg, optionsArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val taskQueue = binaryMessenger.makeBackgroundTaskQueue() + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.setDouble", + codec, + taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val keyArg = args[0] as String + val valueArg = args[1] as Double + val optionsArg = args[2] as SharedPreferencesPigeonOptions + var wrapped: List + try { + api.setDouble(keyArg, valueArg, optionsArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val taskQueue = binaryMessenger.makeBackgroundTaskQueue() + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.setStringList", + codec, + taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val keyArg = args[0] as String + val valueArg = args[1] as List + val optionsArg = args[2] as SharedPreferencesPigeonOptions + var wrapped: List + try { + api.setStringList(keyArg, valueArg, optionsArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.getString", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val keyArg = args[0] as String + val optionsArg = args[1] as SharedPreferencesPigeonOptions + var wrapped: List + try { + wrapped = listOf(api.getString(keyArg, optionsArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.getBool", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val keyArg = args[0] as String + val optionsArg = args[1] as SharedPreferencesPigeonOptions + var wrapped: List + try { + wrapped = listOf(api.getBool(keyArg, optionsArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.getDouble", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val keyArg = args[0] as String + val optionsArg = args[1] as SharedPreferencesPigeonOptions + var wrapped: List + try { + wrapped = listOf(api.getDouble(keyArg, optionsArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.getInt", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val keyArg = args[0] as String + val optionsArg = args[1] as SharedPreferencesPigeonOptions + var wrapped: List + try { + wrapped = listOf(api.getInt(keyArg, optionsArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.getStringList", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val keyArg = args[0] as String + val optionsArg = args[1] as SharedPreferencesPigeonOptions + var wrapped: List + try { + wrapped = listOf(api.getStringList(keyArg, optionsArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val taskQueue = binaryMessenger.makeBackgroundTaskQueue() + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.clear", + codec, + taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val allowListArg = args[0] as List? + val optionsArg = args[1] as SharedPreferencesPigeonOptions + var wrapped: List + try { + api.clear(allowListArg, optionsArg) + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val taskQueue = binaryMessenger.makeBackgroundTaskQueue() + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.getAll", + codec, + taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val allowListArg = args[0] as List? + val optionsArg = args[1] as SharedPreferencesPigeonOptions + var wrapped: List + try { + wrapped = listOf(api.getAll(allowListArg, optionsArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val taskQueue = binaryMessenger.makeBackgroundTaskQueue() + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.getKeys", + codec, + taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val allowListArg = args[0] as List? + val optionsArg = args[1] as SharedPreferencesPigeonOptions + var wrapped: List + try { + wrapped = listOf(api.getKeys(allowListArg, optionsArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/packages/shared_preferences/shared_preferences_android/android/src/main/kotlin/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.kt b/packages/shared_preferences/shared_preferences_android/android/src/main/kotlin/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.kt new file mode 100644 index 00000000000..cb42dec87d9 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_android/android/src/main/kotlin/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.kt @@ -0,0 +1,274 @@ +// 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. + +package io.flutter.plugins.sharedpreferences + +import android.content.Context +import android.util.Base64 +import android.util.Log +import androidx.annotation.VisibleForTesting +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.doublePreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.longPreferencesKey +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.BinaryMessenger +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.ObjectInputStream +import java.io.ObjectOutputStream +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.runBlocking + +const val TAG = "SharedPreferencesPlugin" +const val SHARED_PREFERENCES_NAME = "FlutterSharedPreferences" +const val LIST_PREFIX = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu" + +private val Context.sharedPreferencesDataStore: DataStore by + preferencesDataStore(SHARED_PREFERENCES_NAME) + +/// SharedPreferencesPlugin +class SharedPreferencesPlugin() : FlutterPlugin, SharedPreferencesAsyncApi { + private lateinit var context: Context + + private var listEncoder = ListEncoder() as SharedPreferencesListEncoder + + @VisibleForTesting + constructor(listEncoder: SharedPreferencesListEncoder) : this() { + this.listEncoder = listEncoder + } + + private fun setUp(messenger: BinaryMessenger, context: Context) { + this.context = context + try { + SharedPreferencesAsyncApi.setUp(messenger, this) + } catch (ex: Exception) { + Log.e(TAG, "Received exception while setting up SharedPreferencesPlugin", ex) + } + } + + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + setUp(binding.binaryMessenger, binding.applicationContext) + LegacySharedPreferencesPlugin().onAttachedToEngine(binding) + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + SharedPreferencesAsyncApi.setUp(binding.binaryMessenger, null) + } + + /** Adds property to data store of type bool. */ + override fun setBool(key: String, value: Boolean, options: SharedPreferencesPigeonOptions) { + return runBlocking { + val boolKey = booleanPreferencesKey(key) + context.sharedPreferencesDataStore.edit { preferences -> preferences[boolKey] = value } + } + } + + /** Adds property to data store of type String. */ + override fun setString(key: String, value: String, options: SharedPreferencesPigeonOptions) { + return runBlocking { dataStoreSetString(key, value) } + } + + private suspend fun dataStoreSetString(key: String, value: String) { + val stringKey = stringPreferencesKey(key) + context.sharedPreferencesDataStore.edit { preferences -> preferences[stringKey] = value } + } + + /** Adds property to data store of type int. Converted to Long by pigeon, and saved as such. */ + override fun setInt(key: String, value: Long, options: SharedPreferencesPigeonOptions) { + return runBlocking { + val intKey = longPreferencesKey(key) + context.sharedPreferencesDataStore.edit { preferences -> preferences[intKey] = value } + } + } + + /** Adds property to data store of type double. */ + override fun setDouble(key: String, value: Double, options: SharedPreferencesPigeonOptions) { + return runBlocking { + val doubleKey = doublePreferencesKey(key) + context.sharedPreferencesDataStore.edit { preferences -> preferences[doubleKey] = value } + } + } + + /** Adds property to data store of type List. */ + override fun setStringList( + key: String, + value: List, + options: SharedPreferencesPigeonOptions + ) { + val valueString = LIST_PREFIX + listEncoder.encode(value) + return runBlocking { dataStoreSetString(key, valueString) } + } + + /** Removes all properties from data store. */ + override fun clear(allowList: List?, options: SharedPreferencesPigeonOptions) { + runBlocking { + context.sharedPreferencesDataStore.edit { preferences -> + allowList?.let { list -> + list.forEach { key -> + val preferencesKey = booleanPreferencesKey(key) + preferences.remove(preferencesKey) + } + } ?: preferences.clear() + } + } + } + + /** Gets all properties from data store. */ + override fun getAll( + allowList: List?, + options: SharedPreferencesPigeonOptions + ): Map { + return runBlocking { getPrefs(allowList) } + } + + /** Gets int (as long) at [key] from data store. */ + override fun getInt(key: String, options: SharedPreferencesPigeonOptions): Long? { + val value: Long? + runBlocking { + val preferencesKey = longPreferencesKey(key) + val preferenceFlow: Flow = + context.sharedPreferencesDataStore.data.map { preferences -> preferences[preferencesKey] } + + value = preferenceFlow.firstOrNull() + } + return value + } + + /** Gets bool at [key] from data store. */ + override fun getBool(key: String, options: SharedPreferencesPigeonOptions): Boolean? { + val value: Boolean? + runBlocking { + val preferencesKey = booleanPreferencesKey(key) + val preferenceFlow: Flow = + context.sharedPreferencesDataStore.data.map { preferences -> preferences[preferencesKey] } + + value = preferenceFlow.firstOrNull() + } + return value + } + /** Gets double at [key] from data store. */ + override fun getDouble(key: String, options: SharedPreferencesPigeonOptions): Double? { + val value: Double? + runBlocking { + val preferencesKey = stringPreferencesKey(key) + val preferenceFlow: Flow = + context.sharedPreferencesDataStore.data.map { preferences -> + transformPref(preferences[preferencesKey] as Any?) as Double? + } + + value = preferenceFlow.firstOrNull() + } + return value + } + + /** Gets String at [key] from data store. */ + override fun getString(key: String, options: SharedPreferencesPigeonOptions): String? { + val value: String? + runBlocking { + val preferencesKey = stringPreferencesKey(key) + val preferenceFlow: Flow = + context.sharedPreferencesDataStore.data.map { preferences -> preferences[preferencesKey] } + + value = preferenceFlow.firstOrNull() + } + return value + } + + /** Gets StringList at [key] from data store. */ + override fun getStringList(key: String, options: SharedPreferencesPigeonOptions): List? { + return (transformPref(getString(key, options) as Any?) as List<*>?)?.filterIsInstance() + } + + /** Gets all properties from data store. */ + override fun getKeys( + allowList: List?, + options: SharedPreferencesPigeonOptions + ): List { + val prefs = runBlocking { getPrefs(allowList) } + return prefs.keys.toList() + } + + private suspend fun getPrefs(allowList: List?): Map { + val allowSet = allowList?.toSet() + val filteredMap = mutableMapOf() + + val keys = readAllKeys() + keys?.forEach() { key -> + val value = getValueByKey(key) + if (preferencesFilter(key.toString(), value, allowSet)) { + val transformedValue = transformPref(value) + if (transformedValue != null) { + filteredMap[key.toString()] = transformedValue + } + } + } + return filteredMap + } + + private suspend fun readAllKeys(): Set>? { + val keys = context.sharedPreferencesDataStore.data.map { it.asMap().keys } + return keys.firstOrNull() + } + + private suspend fun getValueByKey(key: Preferences.Key<*>): Any? { + val value = context.sharedPreferencesDataStore.data.map { it[key] } + return value.firstOrNull() + } + + /** + * Returns false for any preferences that are not included in [allowList]. + * + * If no [allowList] is provided, instead returns false for any preferences that are not supported + * by shared_preferences. + */ + private fun preferencesFilter(key: String, value: Any?, allowList: Set?): Boolean { + if (allowList == null) { + return value is Boolean || value is Long || value is String || value is Double + } + + return allowList.contains(key) + } + + /** Transforms preferences that are stored as Strings back to original type. */ + private fun transformPref(value: Any?): Any? { + if (value is String) { + if (value.startsWith(LIST_PREFIX)) { + return listEncoder.decode(value.substring(LIST_PREFIX.length)) + } + } + return value + } + + /** Class that provides tools for encoding and decoding List to String and back. */ + class ListEncoder : SharedPreferencesListEncoder { + override fun encode(list: List): String { + try { + val byteStream = ByteArrayOutputStream() + val stream = ObjectOutputStream(byteStream) + stream.writeObject(list) + stream.flush() + return Base64.encodeToString(byteStream.toByteArray(), 0) + } catch (e: RuntimeException) { + throw RuntimeException(e) + } + } + + override fun decode(listString: String): List { + try { + val byteArray = Base64.decode(listString, 0) + val stream = ObjectInputStream(ByteArrayInputStream(byteArray)) + return (stream.readObject() as List<*>).filterIsInstance() + } catch (e: RuntimeException) { + throw RuntimeException(e) + } + } + } +} 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/LegacySharedPreferencesTest.java similarity index 90% rename from packages/shared_preferences/shared_preferences_android/android/src/test/java/io/flutter/plugins/sharedpreferences/SharedPreferencesTest.java rename to packages/shared_preferences/shared_preferences_android/android/src/test/java/io/flutter/plugins/sharedpreferences/LegacySharedPreferencesTest.java index 19debfeff78..08bbf2fe219 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/LegacySharedPreferencesTest.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; @@ -15,6 +16,7 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.plugin.common.BinaryMessenger; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -24,9 +26,9 @@ import org.mockito.Mock; import org.mockito.Mockito; -public class SharedPreferencesTest { +public class LegacySharedPreferencesTest { - SharedPreferencesPlugin plugin; + LegacySharedPreferencesPlugin plugin; @Mock BinaryMessenger mockMessenger; @Mock FlutterPlugin.FlutterPluginBinding flutterPluginBinding; @@ -42,7 +44,7 @@ public void before() { Mockito.when(flutterPluginBinding.getApplicationContext()).thenReturn(context); Mockito.when(context.getSharedPreferences(anyString(), anyInt())).thenReturn(sharedPrefs); - plugin = new SharedPreferencesPlugin(new ListEncoder()); + plugin = new LegacySharedPreferencesPlugin(new ListEncoder()); plugin.onAttachedToEngine(flutterPluginBinding); } @@ -92,24 +94,24 @@ public void allowList() { addData(); - final List allowList = Arrays.asList("flutter.Language"); + final List allowList = Collections.singletonList("flutter.Language"); Map allData = plugin.getAll("flutter.", allowList); assertEquals(allData.size(), 1); assertEquals(allData.get("flutter.Language"), "Java"); - assertEquals(allData.get("flutter.Counter"), null); + assertNull(allData.get("flutter.Counter")); allData = plugin.getAll("", allowList); assertEquals(allData.size(), 1); assertEquals(allData.get("flutter.Language"), "Java"); - assertEquals(allData.get("flutter.Counter"), null); + assertNull(allData.get("flutter.Counter")); allData = plugin.getAll("prefix.", allowList); assertEquals(allData.size(), 0); - assertEquals(allData.get("flutter.Language"), null); + assertNull(allData.get("flutter.Language")); } @Test @@ -174,7 +176,7 @@ public void clearWithAllowList() { assertEquals(plugin.getAll("", null).size(), 15); - plugin.clear("flutter.", Arrays.asList("flutter.Language")); + plugin.clear("flutter.", Collections.singletonList("flutter.Language")); assertEquals(plugin.getAll("", null).size(), 14); } @@ -240,26 +242,25 @@ public static class FakeSharedPreferencesEditor implements SharedPreferences.Edi } @Override - public @NonNull SharedPreferences.Editor putBoolean( - @NonNull String key, @NonNull boolean value) { + public @NonNull SharedPreferences.Editor putBoolean(@NonNull String key, boolean value) { sharedPrefData.put(key, value); return this; } @Override - public @NonNull SharedPreferences.Editor putInt(@NonNull String key, @NonNull int value) { + public @NonNull SharedPreferences.Editor putInt(@NonNull String key, int value) { sharedPrefData.put(key, value); return this; } @Override - public @NonNull SharedPreferences.Editor putLong(@NonNull String key, @NonNull long value) { + public @NonNull SharedPreferences.Editor putLong(@NonNull String key, long value) { sharedPrefData.put(key, value); return this; } @Override - public @NonNull SharedPreferences.Editor putFloat(@NonNull String key, @NonNull float value) { + public @NonNull SharedPreferences.Editor putFloat(@NonNull String key, float value) { sharedPrefData.put(key, value); return this; } @@ -271,7 +272,7 @@ public static class FakeSharedPreferencesEditor implements SharedPreferences.Edi } @Override - public @NonNull boolean commit() { + public boolean commit() { return true; } @@ -303,27 +304,27 @@ private static class FakeSharedPreferences implements SharedPreferences { // All methods below are not implemented. @Override - public @NonNull boolean contains(@NonNull String key) { + public boolean contains(@NonNull String key) { throw new UnsupportedOperationException("This method is not implemented for testing"); } @Override - public @NonNull boolean getBoolean(@NonNull String key, @NonNull boolean defValue) { + public boolean getBoolean(@NonNull String key, boolean defValue) { throw new UnsupportedOperationException("This method is not implemented for testing"); } @Override - public @NonNull float getFloat(@NonNull String key, @NonNull float defValue) { + public float getFloat(@NonNull String key, float defValue) { throw new UnsupportedOperationException("This method is not implemented for testing"); } @Override - public @NonNull int getInt(@NonNull String key, @NonNull int defValue) { + public int getInt(@NonNull String key, int defValue) { throw new UnsupportedOperationException("This method is not implemented for testing"); } @Override - public @NonNull long getLong(@NonNull String key, @NonNull long defValue) { + public long getLong(@NonNull String key, long defValue) { throw new UnsupportedOperationException("This method is not implemented for testing"); } diff --git a/packages/shared_preferences/shared_preferences_android/android/src/test/kotlin/io/flutter/plugins/sharedpreferences/SharedPreferencesTest.kt b/packages/shared_preferences/shared_preferences_android/android/src/test/kotlin/io/flutter/plugins/sharedpreferences/SharedPreferencesTest.kt new file mode 100644 index 00000000000..30dc92970dd --- /dev/null +++ b/packages/shared_preferences/shared_preferences_android/android/src/test/kotlin/io/flutter/plugins/sharedpreferences/SharedPreferencesTest.kt @@ -0,0 +1,174 @@ +// 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. + +package io.flutter.plugins.sharedpreferences + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.BinaryMessenger +import io.mockk.every +import io.mockk.mockk +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +internal class SharedPreferencesTest { + private val stringKey = "testString" + + private val boolKey = "testBool" + + private val intKey = "testInt" + + private val doubleKey = "testDouble" + + private val listKey = "testList" + + private val testString = "hello world" + + private val testBool = true + + private val testInt = 42L + + private val testDouble = 3.14159 + + private val testList = listOf("foo", "bar") + + private val emptyOptions = SharedPreferencesPigeonOptions() + + private fun pluginSetup(): SharedPreferencesPlugin { + val testContext: Context = ApplicationProvider.getApplicationContext() + + val plugin = SharedPreferencesPlugin() + val binaryMessenger = mockk() + val flutterPluginBinding = mockk() + every { flutterPluginBinding.binaryMessenger } returns binaryMessenger + every { flutterPluginBinding.applicationContext } returns testContext + plugin.onAttachedToEngine(flutterPluginBinding) + return plugin + } + + @Test + fun testSetAndGetBool() { + val plugin = pluginSetup() + plugin.setBool(boolKey, testBool, emptyOptions) + Assert.assertEquals(plugin.getBool(boolKey, emptyOptions), testBool) + } + + @Test + fun testSetAndGetString() { + val plugin = pluginSetup() + plugin.setString(stringKey, testString, emptyOptions) + Assert.assertEquals(plugin.getString(stringKey, emptyOptions), testString) + } + + @Test + fun testSetAndGetInt() { + val plugin = pluginSetup() + plugin.setInt(intKey, testInt, emptyOptions) + Assert.assertEquals(plugin.getInt(intKey, emptyOptions), testInt) + } + + @Test + fun testSetAndGetDouble() { + val plugin = pluginSetup() + plugin.setDouble(doubleKey, testDouble, emptyOptions) + Assert.assertEquals(plugin.getDouble(doubleKey, emptyOptions), testDouble) + } + + @Test + fun testSetAndGetStringList() { + val plugin = pluginSetup() + plugin.setStringList(listKey, testList, emptyOptions) + Assert.assertEquals(plugin.getStringList(listKey, emptyOptions), testList) + } + + @Test + fun testGetKeys() { + val plugin = pluginSetup() + plugin.setBool(boolKey, testBool, emptyOptions) + plugin.setString(stringKey, testString, emptyOptions) + plugin.setInt(intKey, testInt, emptyOptions) + plugin.setDouble(doubleKey, testDouble, emptyOptions) + plugin.setStringList(listKey, testList, emptyOptions) + val keyList = plugin.getKeys(listOf(boolKey, stringKey), emptyOptions) + Assert.assertEquals(keyList.size, 2) + Assert.assertTrue(keyList.contains(stringKey)) + Assert.assertTrue(keyList.contains(boolKey)) + } + + @Test + fun testClear() { + val plugin = pluginSetup() + plugin.setBool(boolKey, testBool, emptyOptions) + plugin.setString(stringKey, testString, emptyOptions) + plugin.setInt(intKey, testInt, emptyOptions) + plugin.setDouble(doubleKey, testDouble, emptyOptions) + plugin.setStringList(listKey, testList, emptyOptions) + + plugin.clear(null, emptyOptions) + + Assert.assertNull(plugin.getBool(boolKey, emptyOptions)) + Assert.assertNull(plugin.getBool(stringKey, emptyOptions)) + Assert.assertNull(plugin.getBool(intKey, emptyOptions)) + Assert.assertNull(plugin.getBool(doubleKey, emptyOptions)) + Assert.assertNull(plugin.getBool(listKey, emptyOptions)) + } + + @Test + fun testGetAll() { + val plugin = pluginSetup() + plugin.setBool(boolKey, testBool, emptyOptions) + plugin.setString(stringKey, testString, emptyOptions) + plugin.setInt(intKey, testInt, emptyOptions) + plugin.setDouble(doubleKey, testDouble, emptyOptions) + plugin.setStringList(listKey, testList, emptyOptions) + + val all = plugin.getAll(null, emptyOptions) + + Assert.assertEquals(all[boolKey], testBool) + Assert.assertEquals(all[stringKey], testString) + Assert.assertEquals(all[intKey], testInt) + Assert.assertEquals(all[doubleKey], testDouble) + Assert.assertEquals(all[listKey], testList) + } + + @Test + fun testClearWithAllowList() { + val plugin = pluginSetup() + plugin.setBool(boolKey, testBool, emptyOptions) + plugin.setString(stringKey, testString, emptyOptions) + plugin.setInt(intKey, testInt, emptyOptions) + plugin.setDouble(doubleKey, testDouble, emptyOptions) + plugin.setStringList(listKey, testList, emptyOptions) + + plugin.clear(listOf(boolKey, stringKey), emptyOptions) + + Assert.assertNull(plugin.getBool(boolKey, emptyOptions)) + Assert.assertNull(plugin.getString(stringKey, emptyOptions)) + Assert.assertNotNull(plugin.getInt(intKey, emptyOptions)) + Assert.assertNotNull(plugin.getDouble(doubleKey, emptyOptions)) + Assert.assertNotNull(plugin.getStringList(listKey, emptyOptions)) + } + + @Test + fun testGetAllWithAllowList() { + val plugin = pluginSetup() + plugin.setBool(boolKey, testBool, emptyOptions) + plugin.setString(stringKey, testString, emptyOptions) + plugin.setInt(intKey, testInt, emptyOptions) + plugin.setDouble(doubleKey, testDouble, emptyOptions) + plugin.setStringList(listKey, testList, emptyOptions) + + val all = plugin.getAll(listOf(boolKey, stringKey), emptyOptions) + + Assert.assertEquals(all[boolKey], testBool) + Assert.assertEquals(all[stringKey], testString) + Assert.assertNull(all[intKey]) + Assert.assertNull(all[doubleKey]) + Assert.assertNull(all[listKey]) + } +} diff --git a/packages/shared_preferences/shared_preferences_android/example/android/app/build.gradle b/packages/shared_preferences/shared_preferences_android/example/android/app/build.gradle index 237a211a6cc..5e851db693e 100644 --- a/packages/shared_preferences/shared_preferences_android/example/android/app/build.gradle +++ b/packages/shared_preferences/shared_preferences_android/example/android/app/build.gradle @@ -57,4 +57,5 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "androidx.datastore:datastore-preferences:1.0.0" } diff --git a/packages/shared_preferences/shared_preferences_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/shared_preferences/shared_preferences_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 21923b54698..6f454ccfb97 100644 --- a/packages/shared_preferences/shared_preferences_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/shared_preferences/shared_preferences_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -14,7 +14,7 @@ * a native java unit test or a java class with a dart integration. * * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests - * for more infomation. + * for more information. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) diff --git a/packages/shared_preferences/shared_preferences_android/example/android/build.gradle b/packages/shared_preferences/shared_preferences_android/example/android/build.gradle index 4dec761be7b..af575c8b5cd 100644 --- a/packages/shared_preferences/shared_preferences_android/example/android/build.gradle +++ b/packages/shared_preferences/shared_preferences_android/example/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.8.10' repositories { google() mavenCentral() diff --git a/packages/shared_preferences/shared_preferences_android/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/shared_preferences/shared_preferences_android/example/android/gradle/wrapper/gradle-wrapper.properties index aeaff6f869f..c48e7025d19 100644 --- a/packages/shared_preferences/shared_preferences_android/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/shared_preferences/shared_preferences_android/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Jun 23 08:50:38 CEST 2017 +#Wed Jan 17 19:21:34 PST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/packages/shared_preferences/shared_preferences_android/example/integration_test/shared_preferences_test.dart b/packages/shared_preferences/shared_preferences_android/example/integration_test/shared_preferences_test.dart index 2bb16133840..0c4527f4670 100644 --- a/packages/shared_preferences/shared_preferences_android/example/integration_test/shared_preferences_test.dart +++ b/packages/shared_preferences/shared_preferences_android/example/integration_test/shared_preferences_test.dart @@ -5,6 +5,8 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:shared_preferences_android/shared_preferences_android.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; import 'package:shared_preferences_platform_interface/types.dart'; @@ -462,9 +464,8 @@ void main() { expect(values['Int'], writeCount); }); - testWidgets('string clash with lists, big integers and doubles', - (WidgetTester _) async { - const String key = 'akey'; + testWidgets('string clash with lists and doubles', (WidgetTester _) async { + const String key = 'aKey'; const String value = 'a string value'; await preferences.clearWithParameters( ClearParameters( @@ -477,8 +478,6 @@ void main() { const List specialPrefixes = [ // Prefix for lists: 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu', - // Prefix for big integers: - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBCaWdJbnRlZ2Vy', // Prefix for doubles: 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBEb3VibGUu', ]; @@ -495,4 +494,199 @@ void main() { } }); }); + + group('shared_preferences_async', () { + const SharedPreferencesAsyncAndroidOptions emptyOptions = + SharedPreferencesAsyncAndroidOptions(); + + const String stringKey = 'testString'; + const String boolKey = 'testBool'; + const String intKey = 'testInt'; + const String doubleKey = 'testDouble'; + const String listKey = 'testList'; + + const String testString = 'hello world'; + const bool testBool = true; + const int testInt = 42; + const double testDouble = 3.14159; + const List testList = ['foo', 'bar']; + + Future getPreferences() async { + final SharedPreferencesAsyncPlatform preferences = + SharedPreferencesAsyncPlatform.instance!; + await preferences.clear( + const ClearPreferencesParameters(filter: PreferencesFilters()), + emptyOptions); + return preferences; + } + + testWidgets('set and get String', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setString(stringKey, testString, emptyOptions); + expect(await preferences.getString(stringKey, emptyOptions), testString); + }); + + testWidgets('set and get bool', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setBool(boolKey, testBool, emptyOptions); + expect(await preferences.getBool(boolKey, emptyOptions), testBool); + }); + + testWidgets('set and get int', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setInt(intKey, testInt, emptyOptions); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + }); + + testWidgets('set and get double', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + }); + + testWidgets('set and get StringList', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setStringList(listKey, testList, emptyOptions); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); + + testWidgets('getPreferences', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, + ); + + expect(gotAll.length, 5); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + expect(gotAll[intKey], testInt); + expect(gotAll[doubleKey], testDouble); + expect(gotAll[listKey], testList); + }); + + testWidgets('getPreferences with filter', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + + expect(gotAll.length, 2); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + }); + + testWidgets('getKeys', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, + ); + + expect(keys.length, 5); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + expect(keys, contains(intKey)); + expect(keys, contains(doubleKey)); + expect(keys, contains(listKey)); + }); + + testWidgets('getKeys with filter', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + + expect(keys.length, 2); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + }); + + testWidgets('clear', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + await preferences.clear( + const ClearPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, + ); + + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), null); + expect(await preferences.getDouble(doubleKey, emptyOptions), null); + expect(await preferences.getStringList(listKey, emptyOptions), null); + }); + + testWidgets('clear with filter', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + await preferences.clear( + const ClearPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); + }); } diff --git a/packages/shared_preferences/shared_preferences_android/example/lib/main.dart b/packages/shared_preferences/shared_preferences_android/example/lib/main.dart index 69abfbd506d..5bf8d060920 100644 --- a/packages/shared_preferences/shared_preferences_android/example/lib/main.dart +++ b/packages/shared_preferences/shared_preferences_android/example/lib/main.dart @@ -5,7 +5,8 @@ // ignore_for_file: public_member_api_docs import 'package:flutter/material.dart'; -import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; +import 'package:shared_preferences_android/shared_preferences_android.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; void main() { runApp(const MyApp()); @@ -31,32 +32,36 @@ class SharedPreferencesDemo extends StatefulWidget { } class SharedPreferencesDemoState extends State { - final SharedPreferencesStorePlatform _prefs = - SharedPreferencesStorePlatform.instance; + final SharedPreferencesAsyncPlatform _prefs = + SharedPreferencesAsyncPlatform.instance!; + final SharedPreferencesAsyncAndroidOptions options = + const SharedPreferencesAsyncAndroidOptions(); + static const String _counterKey = 'counter'; late Future _counter; - // Includes the prefix because this is using the platform interface directly, - // but the prefix (which the native code assumes is present) is added by the - // app-facing package. - static const String _prefKey = 'flutter.counter'; - Future _incrementCounter() async { - final Map values = await _prefs.getAll(); - final int counter = ((values[_prefKey] as int?) ?? 0) + 1; + final int? value = await _prefs.getInt(_counterKey, options); + final int counter = (value ?? 0) + 1; setState(() { - _counter = _prefs.setValue('Int', _prefKey, counter).then((bool success) { + _counter = _prefs.setInt(_counterKey, counter, options).then((_) { return counter; }); }); } + Future _getAndSetCounter() async { + setState(() { + _counter = _prefs.getInt(_counterKey, options).then((int? counter) { + return counter ?? 0; + }); + }); + } + @override void initState() { super.initState(); - _counter = _prefs.getAll().then((Map values) { - return (values[_prefKey] as int?) ?? 0; - }); + _getAndSetCounter(); } @override diff --git a/packages/shared_preferences/shared_preferences_android/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_android/example/pubspec.yaml index 0bdf6908628..632f949762f 100644 --- a/packages/shared_preferences/shared_preferences_android/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_android/example/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - shared_preferences_platform_interface: ^2.3.0 + shared_preferences_platform_interface: ^2.4.0 dev_dependencies: flutter_test: 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 352d36484e2..3a614b8f377 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 @@ -2,101 +2,5 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; -import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; -import 'package:shared_preferences_platform_interface/types.dart'; - -import 'src/messages.g.dart'; - -/// The Android implementation of [SharedPreferencesStorePlatform]. -/// -/// This class implements the `package:shared_preferences` functionality for Android. -class SharedPreferencesAndroid extends SharedPreferencesStorePlatform { - /// Creates a new plugin implementation instance. - SharedPreferencesAndroid({ - @visibleForTesting SharedPreferencesApi? api, - }) : _api = api ?? SharedPreferencesApi(); - - final SharedPreferencesApi _api; - - /// Registers this class as the default instance of [SharedPreferencesStorePlatform]. - static void registerWith() { - SharedPreferencesStorePlatform.instance = SharedPreferencesAndroid(); - } - - static const String _defaultPrefix = 'flutter.'; - - @override - Future remove(String key) async { - return _api.remove(key); - } - - @override - Future setValue(String valueType, String key, Object value) async { - switch (valueType) { - 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); - } - // TODO(tarrinneal): change to ArgumentError across all platforms. - throw PlatformException( - code: 'InvalidOperation', - message: '"$valueType" is not a supported type.'); - } - - @override - Future clear() async { - return clearWithParameters( - ClearParameters( - filter: PreferencesFilter(prefix: _defaultPrefix), - ), - ); - } - - @override - Future clearWithPrefix(String prefix) async { - return clearWithParameters( - ClearParameters(filter: PreferencesFilter(prefix: prefix))); - } - - @override - Future clearWithParameters(ClearParameters parameters) async { - final PreferencesFilter filter = parameters.filter; - return _api.clear( - filter.prefix, - filter.allowList?.toList(), - ); - } - - @override - Future> getAll() async { - return getAllWithParameters( - GetAllParameters( - filter: PreferencesFilter(prefix: _defaultPrefix), - ), - ); - } - - @override - Future> getAllWithPrefix(String prefix) async { - return getAllWithParameters( - GetAllParameters(filter: PreferencesFilter(prefix: prefix))); - } - - @override - Future> getAllWithParameters( - GetAllParameters parameters) async { - final PreferencesFilter filter = parameters.filter; - final Map data = - await _api.getAll(filter.prefix, filter.allowList?.toList()); - return data.cast(); - } -} +export 'src/shared_preferences_android.dart'; +export 'src/shared_preferences_async_android.dart'; diff --git a/packages/shared_preferences/shared_preferences_android/lib/src/messages.g.dart b/packages/shared_preferences/shared_preferences_android/lib/src/messages.g.dart index 1e843098973..3a0034796d3 100644 --- a/packages/shared_preferences/shared_preferences_android/lib/src/messages.g.dart +++ b/packages/shared_preferences/shared_preferences_android/lib/src/messages.g.dart @@ -1,9 +1,9 @@ // 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 (v9.2.5), do not edit directly. +// Autogenerated from Pigeon (v16.0.4), 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 +// 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, no_leading_underscores_for_local_identifiers import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; @@ -11,238 +11,263 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + 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; + : __pigeon_binaryMessenger = binaryMessenger; + final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec codec = StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = + StandardMessageCodec(); /// Removes property from shared preferences data set. - 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) { + Future remove(String key) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesApi.remove'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([key]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], ); - } 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) { + } else if (__pigeon_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?)!; + return (__pigeon_replyList[0] as bool?)!; } } /// Adds property to shared preferences data set of type 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) { + Future setBool(String key, bool value) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesApi.setBool'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([key, value]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], ); - } 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) { + } else if (__pigeon_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?)!; + return (__pigeon_replyList[0] as bool?)!; } } /// Adds property to shared preferences data set of type String. - 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) { + Future setString(String key, String value) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesApi.setString'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([key, value]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { throw PlatformException( - code: replyList[0]! as String, - message: replyList[1] as String?, - details: replyList[2], + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], ); - } else if (replyList[0] == null) { + } else if (__pigeon_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?)!; + return (__pigeon_replyList[0] as bool?)!; } } /// Adds property to shared preferences data set of type int. - Future setInt(String arg_key, int 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) { + Future setInt(String key, int value) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesApi.setInt'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([key, value]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { throw PlatformException( - code: replyList[0]! as String, - message: replyList[1] as String?, - details: replyList[2], + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], ); - } else if (replyList[0] == null) { + } else if (__pigeon_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?)!; + return (__pigeon_replyList[0] as bool?)!; } } /// Adds property to shared preferences data set of type double. - 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) { + Future setDouble(String key, double value) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesApi.setDouble'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([key, value]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { throw PlatformException( - code: replyList[0]! as String, - message: replyList[1] as String?, - details: replyList[2], + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], ); - } else if (replyList[0] == null) { + } else if (__pigeon_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?)!; + return (__pigeon_replyList[0] as bool?)!; } } /// Adds property to shared preferences data set of type List. - 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) { + Future setStringList(String key, List value) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesApi.setStringList'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([key, value]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { throw PlatformException( - code: replyList[0]! as String, - message: replyList[1] as String?, - details: replyList[2], + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], ); - } else if (replyList[0] == null) { + } else if (__pigeon_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?)!; + return (__pigeon_replyList[0] as bool?)!; } } /// Removes all properties from shared preferences data set with matching prefix. - Future clear(String arg_prefix, List? arg_allowList) async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.SharedPreferencesApi.clear', codec, - binaryMessenger: _binaryMessenger); - final List? replyList = await channel - .send([arg_prefix, arg_allowList]) as List?; - if (replyList == null) { + Future clear(String prefix, List? allowList) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesApi.clear'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([prefix, allowList]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], ); - } 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) { + } else if (__pigeon_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?)!; + return (__pigeon_replyList[0] as bool?)!; } } /// Gets all properties from shared preferences data set with matching prefix. Future> getAll( - String arg_prefix, List? arg_allowList) async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.SharedPreferencesApi.getAll', codec, - binaryMessenger: _binaryMessenger); - final List? replyList = await channel - .send([arg_prefix, arg_allowList]) as List?; - if (replyList == null) { - throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', - ); - } else if (replyList.length > 1) { + String prefix, List? allowList) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesApi.getAll'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([prefix, allowList]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { throw PlatformException( - code: replyList[0]! as String, - message: replyList[1] as String?, - details: replyList[2], + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], ); - } else if (replyList[0] == null) { + } else if (__pigeon_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(); + return (__pigeon_replyList[0] as Map?)! + .cast(); } } } diff --git a/packages/shared_preferences/shared_preferences_android/lib/src/messages_async.g.dart b/packages/shared_preferences/shared_preferences_android/lib/src/messages_async.g.dart new file mode 100644 index 00000000000..d355cfd916d --- /dev/null +++ b/packages/shared_preferences/shared_preferences_android/lib/src/messages_async.g.dart @@ -0,0 +1,424 @@ +// 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 (v16.0.5), 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, no_leading_underscores_for_local_identifiers + +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'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +class SharedPreferencesPigeonOptions { + SharedPreferencesPigeonOptions({ + this.fileKey, + }); + + String? fileKey; + + Object encode() { + return [ + fileKey, + ]; + } + + static SharedPreferencesPigeonOptions decode(Object result) { + result as List; + return SharedPreferencesPigeonOptions( + fileKey: result[0] as String?, + ); + } +} + +class _SharedPreferencesAsyncApiCodec extends StandardMessageCodec { + const _SharedPreferencesAsyncApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is SharedPreferencesPigeonOptions) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return SharedPreferencesPigeonOptions.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class SharedPreferencesAsyncApi { + /// Constructor for [SharedPreferencesAsyncApi]. 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. + SharedPreferencesAsyncApi({BinaryMessenger? binaryMessenger}) + : __pigeon_binaryMessenger = binaryMessenger; + final BinaryMessenger? __pigeon_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = + _SharedPreferencesAsyncApiCodec(); + + /// Adds property to shared preferences data set of type bool. + Future setBool( + String key, bool value, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.setBool'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([key, value, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Adds property to shared preferences data set of type String. + Future setString( + String key, String value, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.setString'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([key, value, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Adds property to shared preferences data set of type int. + Future setInt( + String key, int value, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.setInt'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([key, value, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Adds property to shared preferences data set of type double. + Future setDouble( + String key, double value, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.setDouble'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([key, value, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Adds property to shared preferences data set of type List. + Future setStringList(String key, List value, + SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.setStringList'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([key, value, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Gets individual String value stored with [key], if any. + Future getString( + String key, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.getString'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([key, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as String?); + } + } + + /// Gets individual void value stored with [key], if any. + Future getBool( + String key, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.getBool'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([key, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as bool?); + } + } + + /// Gets individual double value stored with [key], if any. + Future getDouble( + String key, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.getDouble'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([key, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as double?); + } + } + + /// Gets individual int value stored with [key], if any. + Future getInt( + String key, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.getInt'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([key, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as int?); + } + } + + /// Gets individual List value stored with [key], if any. + Future?> getStringList( + String key, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.getStringList'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([key, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as List?)?.cast(); + } + } + + /// Removes all properties from shared preferences data set with matching prefix. + Future clear( + List? allowList, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.clear'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([allowList, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Gets all properties from shared preferences data set with matching prefix. + Future> getAll( + List? allowList, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.getAll'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([allowList, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as Map?)! + .cast(); + } + } + + /// Gets all properties from shared preferences data set with matching prefix. + Future> getKeys( + List? allowList, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.getKeys'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([allowList, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as List?)!.cast(); + } + } +} diff --git a/packages/shared_preferences/shared_preferences_android/lib/src/shared_preferences_android.dart b/packages/shared_preferences/shared_preferences_android/lib/src/shared_preferences_android.dart new file mode 100644 index 00000000000..67cc0835635 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_android/lib/src/shared_preferences_android.dart @@ -0,0 +1,105 @@ +// 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:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; +import 'package:shared_preferences_platform_interface/types.dart'; + +import 'messages.g.dart'; +import 'shared_preferences_async_android.dart'; + +/// The Android implementation of [SharedPreferencesStorePlatform]. +/// +/// This class implements the `package:shared_preferences` functionality for Android. +class SharedPreferencesAndroid extends SharedPreferencesStorePlatform { + /// Creates a new plugin implementation instance. + SharedPreferencesAndroid({ + @visibleForTesting SharedPreferencesApi? api, + }) : _api = api ?? SharedPreferencesApi(); + + final SharedPreferencesApi _api; + + /// Registers this class as the default instance of [SharedPreferencesStorePlatform]. + static void registerWith() { + SharedPreferencesStorePlatform.instance = SharedPreferencesAndroid(); + // A temporary work-around for having two plugins contained in a single package. + SharedPreferencesAsyncAndroid.registerWith(); + } + + static const String _defaultPrefix = 'flutter.'; + + @override + Future remove(String key) async { + return _api.remove(key); + } + + @override + Future setValue(String valueType, String key, Object value) async { + switch (valueType) { + 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); + } + // TODO(tarrinneal): change to ArgumentError across all platforms. + throw PlatformException( + code: 'InvalidOperation', + message: '"$valueType" is not a supported type.'); + } + + @override + Future clear() async { + return clearWithParameters( + ClearParameters( + filter: PreferencesFilter(prefix: _defaultPrefix), + ), + ); + } + + @override + Future clearWithPrefix(String prefix) async { + return clearWithParameters( + ClearParameters(filter: PreferencesFilter(prefix: prefix))); + } + + @override + Future clearWithParameters(ClearParameters parameters) async { + final PreferencesFilter filter = parameters.filter; + return _api.clear( + filter.prefix, + filter.allowList?.toList(), + ); + } + + @override + Future> getAll() async { + return getAllWithParameters( + GetAllParameters( + filter: PreferencesFilter(prefix: _defaultPrefix), + ), + ); + } + + @override + Future> getAllWithPrefix(String prefix) async { + return getAllWithParameters( + GetAllParameters(filter: PreferencesFilter(prefix: prefix))); + } + + @override + Future> getAllWithParameters( + GetAllParameters parameters) async { + final PreferencesFilter filter = parameters.filter; + final Map data = + await _api.getAll(filter.prefix, filter.allowList?.toList()); + return data.cast(); + } +} diff --git a/packages/shared_preferences/shared_preferences_android/lib/src/shared_preferences_async_android.dart b/packages/shared_preferences/shared_preferences_android/lib/src/shared_preferences_async_android.dart new file mode 100644 index 00000000000..7b9b8fa6ade --- /dev/null +++ b/packages/shared_preferences/shared_preferences_android/lib/src/shared_preferences_async_android.dart @@ -0,0 +1,195 @@ +// 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:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; +import 'package:shared_preferences_platform_interface/types.dart'; + +import 'messages_async.g.dart'; + +const String _listPrefix = 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu'; + +/// The Android implementation of [SharedPreferencesAsyncPlatform]. +/// +/// This class implements the `package:shared_preferences` functionality for Android. +base class SharedPreferencesAsyncAndroid + extends SharedPreferencesAsyncPlatform { + /// Creates a new plugin implementation instance. + SharedPreferencesAsyncAndroid({ + @visibleForTesting SharedPreferencesAsyncApi? api, + }) : _api = api ?? SharedPreferencesAsyncApi(); + + final SharedPreferencesAsyncApi _api; + + /// Registers this class as the default instance of [SharedPreferencesAsyncPlatform]. + static void registerWith() { + SharedPreferencesAsyncPlatform.instance = SharedPreferencesAsyncAndroid(); + } + + /// Returns a SharedPreferencesPigeonOptions for sending to platform. + SharedPreferencesPigeonOptions _convertOptionsToPigeonOptions( + SharedPreferencesOptions options) { + return SharedPreferencesPigeonOptions(); + } + + @override + Future> getKeys( + GetPreferencesParameters parameters, + SharedPreferencesOptions options, + ) async { + final PreferencesFilters filter = parameters.filter; + // TODO(tarrinneal): Remove cast once https://github.com/flutter/flutter/issues/97848 + // is fixed. In practice, the values will never be null, and the native implementation assumes that. + return (await _api.getKeys( + filter.allowList?.toList(), + _convertOptionsToPigeonOptions(options), + )) + .cast() + .toSet(); + } + + @override + Future setString( + String key, + String value, + SharedPreferencesOptions options, + ) async { + if (value.startsWith(_listPrefix)) { + throw ArgumentError( + 'StorageError: This string cannot be stored as it clashes with special identifier prefixes'); + } + + return _api.setString(key, value, _convertOptionsToPigeonOptions(options)); + } + + @override + Future setInt( + String key, + int value, + SharedPreferencesOptions options, + ) async { + return _api.setInt(key, value, _convertOptionsToPigeonOptions(options)); + } + + @override + Future setDouble( + String key, + double value, + SharedPreferencesOptions options, + ) async { + return _api.setDouble(key, value, _convertOptionsToPigeonOptions(options)); + } + + @override + Future setBool( + String key, + bool value, + SharedPreferencesOptions options, + ) async { + return _api.setBool(key, value, _convertOptionsToPigeonOptions(options)); + } + + @override + Future setStringList( + String key, + List value, + SharedPreferencesOptions options, + ) async { + return _api.setStringList( + key, value, _convertOptionsToPigeonOptions(options)); + } + + @override + Future getString( + String key, + SharedPreferencesOptions options, + ) async { + return _convertKnownExceptions(() async => + _api.getString(key, _convertOptionsToPigeonOptions(options))); + } + + @override + Future getBool( + String key, + SharedPreferencesOptions options, + ) async { + return _convertKnownExceptions( + () async => _api.getBool(key, _convertOptionsToPigeonOptions(options))); + } + + @override + Future getDouble( + String key, + SharedPreferencesOptions options, + ) async { + return _convertKnownExceptions(() async => + _api.getDouble(key, _convertOptionsToPigeonOptions(options))); + } + + @override + Future getInt( + String key, + SharedPreferencesOptions options, + ) async { + return _convertKnownExceptions( + () async => _api.getInt(key, _convertOptionsToPigeonOptions(options))); + } + + @override + Future?> getStringList( + String key, + SharedPreferencesOptions options, + ) async { + // TODO(tarrinneal): Remove cast once https://github.com/flutter/flutter/issues/97848 + // is fixed. In practice, the values will never be null, and the native implementation assumes that. + return _convertKnownExceptions>(() async => + (await _api.getStringList(key, _convertOptionsToPigeonOptions(options))) + ?.cast()); + } + + Future _convertKnownExceptions(Future Function() method) async { + try { + final T? value = await method(); + return value; + } on PlatformException catch (e) { + if (e.code == 'ClassCastException') { + throw TypeError(); + } else { + rethrow; + } + } + } + + @override + Future clear( + ClearPreferencesParameters parameters, + SharedPreferencesOptions options, + ) async { + final PreferencesFilters filter = parameters.filter; + return _api.clear( + filter.allowList?.toList(), + _convertOptionsToPigeonOptions(options), + ); + } + + @override + Future> getPreferences( + GetPreferencesParameters parameters, + SharedPreferencesOptions options, + ) async { + final PreferencesFilters filter = parameters.filter; + final Map data = await _api.getAll( + filter.allowList?.toList(), + _convertOptionsToPigeonOptions(options), + ); + return data.cast(); + } +} + +/// Options for the Android specific SharedPreferences plugin. +class SharedPreferencesAsyncAndroidOptions extends SharedPreferencesOptions { + /// Constructor for SharedPreferencesAsyncAndroidOptions. + const SharedPreferencesAsyncAndroidOptions(); +} diff --git a/packages/shared_preferences/shared_preferences_android/pigeons/messages_async.dart b/packages/shared_preferences/shared_preferences_android/pigeons/messages_async.dart new file mode 100644 index 00000000000..5334cc042f5 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_android/pigeons/messages_async.dart @@ -0,0 +1,118 @@ +// 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_async.dart', + kotlinOut: + 'android/src/main/kotlin/io/flutter/plugins/sharedpreferences/MessagesAsync.g.kt', + kotlinOptions: KotlinOptions( + package: 'io.flutter.plugins.sharedpreferences', + errorClassName: 'SharedPreferencesError', + ), + dartOut: 'lib/src/messages_async.g.dart', + copyrightHeader: 'pigeons/copyright.txt', +)) +class SharedPreferencesPigeonOptions { + SharedPreferencesPigeonOptions({ + this.fileKey, + }); + String? fileKey; +} + +@HostApi(dartHostTestHandler: 'TestSharedPreferencesAsyncApi') +abstract class SharedPreferencesAsyncApi { + /// Adds property to shared preferences data set of type bool. + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + void setBool(String key, bool value, SharedPreferencesPigeonOptions options); + + /// Adds property to shared preferences data set of type String. + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + void setString( + String key, + String value, + SharedPreferencesPigeonOptions options, + ); + + /// Adds property to shared preferences data set of type int. + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + void setInt( + String key, + int value, + SharedPreferencesPigeonOptions options, + ); + + /// Adds property to shared preferences data set of type double. + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + void setDouble( + String key, + double value, + SharedPreferencesPigeonOptions options, + ); + + /// Adds property to shared preferences data set of type List. + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + void setStringList( + String key, + List value, + SharedPreferencesPigeonOptions options, + ); + + /// Gets individual String value stored with [key], if any. + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + String? getString( + String key, + SharedPreferencesPigeonOptions options, + ); + + /// Gets individual void value stored with [key], if any. + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + bool? getBool( + String key, + SharedPreferencesPigeonOptions options, + ); + + /// Gets individual double value stored with [key], if any. + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + double? getDouble( + String key, + SharedPreferencesPigeonOptions options, + ); + + /// Gets individual int value stored with [key], if any. + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + int? getInt( + String key, + SharedPreferencesPigeonOptions options, + ); + + /// Gets individual List value stored with [key], if any. + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + List? getStringList( + String key, + SharedPreferencesPigeonOptions options, + ); + + /// Removes all properties from shared preferences data set with matching prefix. + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + void clear( + List? allowList, + SharedPreferencesPigeonOptions options, + ); + + /// Gets all properties from shared preferences data set with matching prefix. + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + Map getAll( + List? allowList, + SharedPreferencesPigeonOptions options, + ); + + /// Gets all properties from shared preferences data set with matching prefix. + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + List getKeys( + List? allowList, + SharedPreferencesPigeonOptions options, + ); +} diff --git a/packages/shared_preferences/shared_preferences_android/pubspec.yaml b/packages/shared_preferences/shared_preferences_android/pubspec.yaml index 711135b9f61..bd1adb8cb9c 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/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.2.4 +version: 2.3.0 environment: sdk: ^3.4.0 @@ -20,12 +20,12 @@ flutter: dependencies: flutter: sdk: flutter - shared_preferences_platform_interface: ^2.3.0 + shared_preferences_platform_interface: ^2.4.0 dev_dependencies: flutter_test: sdk: flutter - pigeon: ^9.2.3 + pigeon: ^16.0.4 topics: - persistence 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 421ccfe7ea8..c30fa08d18d 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 @@ -49,7 +49,7 @@ void main() { plugin = SharedPreferencesAndroid(api: api); }); - test('registerWith', () { + test('registerWith', () async { SharedPreferencesAndroid.registerWith(); expect(SharedPreferencesStorePlatform.instance, isA()); @@ -211,7 +211,7 @@ void main() { expect(api.items['flutter.StringList'], ['hi']); }); - test('setValue with unsupported type', () { + test('setValue with unsupported type', () async { expect(() async { await plugin.setValue('Map', 'flutter.key', {}); }, throwsA(isA())); diff --git a/packages/shared_preferences/shared_preferences_android/test/shared_preferences_async_test.dart b/packages/shared_preferences/shared_preferences_android/test/shared_preferences_async_test.dart new file mode 100755 index 00000000000..45ee340a7cf --- /dev/null +++ b/packages/shared_preferences/shared_preferences_android/test/shared_preferences_async_test.dart @@ -0,0 +1,300 @@ +// 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:flutter_test/flutter_test.dart'; +import 'package:shared_preferences_android/shared_preferences_android.dart'; +import 'package:shared_preferences_android/src/messages_async.g.dart'; +import 'package:shared_preferences_platform_interface/types.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const String stringKey = 'testString'; + const String boolKey = 'testBool'; + const String intKey = 'testInt'; + const String doubleKey = 'testDouble'; + const String listKey = 'testList'; + + const String testString = 'hello world'; + const bool testBool = true; + const int testInt = 42; + const double testDouble = 3.14159; + const List testList = ['foo', 'bar']; + + const SharedPreferencesAsyncAndroidOptions emptyOptions = + SharedPreferencesAsyncAndroidOptions(); + + SharedPreferencesAsyncAndroid getPreferences() { + final _FakeSharedPreferencesApi api = _FakeSharedPreferencesApi(); + final SharedPreferencesAsyncAndroid preferences = + SharedPreferencesAsyncAndroid(api: api); + + return preferences; + } + + test('set and get String', () async { + final SharedPreferencesAsyncAndroid preferences = getPreferences(); + + await preferences.setString(stringKey, testString, emptyOptions); + expect(await preferences.getString(stringKey, emptyOptions), testString); + }); + + test('set and get bool', () async { + final SharedPreferencesAsyncAndroid preferences = getPreferences(); + + await preferences.setBool(boolKey, testBool, emptyOptions); + expect(await preferences.getBool(boolKey, emptyOptions), testBool); + }); + + test('set and get int', () async { + final SharedPreferencesAsyncAndroid preferences = getPreferences(); + + await preferences.setInt(intKey, testInt, emptyOptions); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + }); + + test('set and get double', () async { + final SharedPreferencesAsyncAndroid preferences = getPreferences(); + + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + }); + + test('set and get StringList', () async { + final SharedPreferencesAsyncAndroid preferences = getPreferences(); + + await preferences.setStringList(listKey, testList, emptyOptions); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); + + test('getPreferences', () async { + final SharedPreferencesAsyncAndroid preferences = getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions); + + expect(gotAll.length, 5); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + expect(gotAll[intKey], testInt); + expect(gotAll[doubleKey], testDouble); + expect(gotAll[listKey], testList); + }); + + test('getPreferences with filter', () async { + final SharedPreferencesAsyncAndroid preferences = getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters( + filter: + PreferencesFilters(allowList: {stringKey, boolKey})), + emptyOptions); + + expect(gotAll.length, 2); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + }); + + test('getKeys', () async { + final SharedPreferencesAsyncAndroid preferences = getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, + ); + + expect(keys.length, 5); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + expect(keys, contains(intKey)); + expect(keys, contains(doubleKey)); + expect(keys, contains(listKey)); + }); + + test('getKeys with filter', () async { + final SharedPreferencesAsyncAndroid preferences = getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + + expect(keys.length, 2); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + }); + + test('clear', () async { + final SharedPreferencesAsyncAndroid preferences = getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + await preferences.clear( + const ClearPreferencesParameters(filter: PreferencesFilters()), + emptyOptions); + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), null); + expect(await preferences.getDouble(doubleKey, emptyOptions), null); + expect(await preferences.getStringList(listKey, emptyOptions), null); + }); + + test('clear with filter', () async { + final SharedPreferencesAsyncAndroid preferences = getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + await preferences.clear( + const ClearPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); +} + +class _FakeSharedPreferencesApi implements SharedPreferencesAsyncApi { + final Map items = {}; + + @override + Future clear( + List? allowList, SharedPreferencesPigeonOptions options) async { + if (allowList != null) { + items.removeWhere((String key, _) => allowList.contains(key)); + } else { + items.clear(); + } + + return true; + } + + @override + Future> getAll( + List? allowList, SharedPreferencesPigeonOptions options) async { + final Map filteredItems = {...items}; + if (allowList != null) { + filteredItems.removeWhere((String key, _) => !allowList.contains(key)); + } + return filteredItems; + } + + @override + Future getBool( + String key, SharedPreferencesPigeonOptions options) async { + return items[key] as bool?; + } + + @override + Future getDouble( + String key, SharedPreferencesPigeonOptions options) async { + return items[key] as double?; + } + + @override + Future getInt( + String key, SharedPreferencesPigeonOptions options) async { + return items[key] as int?; + } + + @override + Future> getKeys( + List? allowList, SharedPreferencesPigeonOptions options) async { + final List filteredItems = items.keys.toList(); + if (allowList != null) { + filteredItems.removeWhere((String key) => !allowList.contains(key)); + } + return filteredItems; + } + + @override + Future getString( + String key, SharedPreferencesPigeonOptions options) async { + return items[key] as String?; + } + + @override + Future?> getStringList( + String key, SharedPreferencesPigeonOptions options) async { + return items[key] as List?; + } + + @override + Future setBool( + String key, bool value, SharedPreferencesPigeonOptions options) async { + items[key] = value; + return true; + } + + @override + Future setDouble( + String key, double value, SharedPreferencesPigeonOptions options) async { + items[key] = value; + return true; + } + + @override + Future setInt( + String key, int value, SharedPreferencesPigeonOptions options) async { + items[key] = value; + return true; + } + + @override + Future setString( + String key, String value, SharedPreferencesPigeonOptions options) async { + items[key] = value; + return true; + } + + @override + Future setStringList(String key, List value, + SharedPreferencesPigeonOptions options) async { + items[key] = value; + return true; + } +} diff --git a/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md b/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md index e487278c498..a3d1b1531e2 100644 --- a/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.5.0 + +* Adds new `SharedPreferencesAsyncFoundation` API. + ## 2.4.0 * Adds Swift Package Manager compatibility. diff --git a/packages/shared_preferences/shared_preferences_foundation/darwin/Tests/RunnerTests.swift b/packages/shared_preferences/shared_preferences_foundation/darwin/Tests/RunnerTests.swift index 42a1d00d835..88d38022d61 100644 --- a/packages/shared_preferences/shared_preferences_foundation/darwin/Tests/RunnerTests.swift +++ b/packages/shared_preferences/shared_preferences_foundation/darwin/Tests/RunnerTests.swift @@ -13,11 +13,17 @@ import XCTest #endif class RunnerTests: XCTestCase { + let testKey = "foo" + let testKeyTwo = "baz" + let testValue = "bar" + + // Legacy system tests. + let prefixes: [String] = ["aPrefix", ""] func testSetAndGet() throws { for aPrefix in prefixes { - let plugin = SharedPreferencesPlugin() + let plugin = LegacySharedPreferencesPlugin() plugin.setBool(key: "\(aPrefix)aBool", value: true) plugin.setDouble(key: "\(aPrefix)aDouble", value: 3.14) @@ -36,7 +42,7 @@ class RunnerTests: XCTestCase { func testGetWithAllowList() throws { for aPrefix in prefixes { - let plugin = SharedPreferencesPlugin() + let plugin = LegacySharedPreferencesPlugin() plugin.setBool(key: "\(aPrefix)aBool", value: true) plugin.setDouble(key: "\(aPrefix)aDouble", value: 3.14) @@ -55,8 +61,8 @@ class RunnerTests: XCTestCase { func testRemove() throws { for aPrefix in prefixes { - let plugin = SharedPreferencesPlugin() - let testKey = "\(aPrefix)foo" + let plugin = LegacySharedPreferencesPlugin() + let testKey = "\(aPrefix)\(testKey)" plugin.setValue(key: testKey, value: 42) // Make sure there is something to remove, so the test can't pass due to a set failure. @@ -73,8 +79,8 @@ class RunnerTests: XCTestCase { func testClearWithNoAllowlist() throws { for aPrefix in prefixes { - let plugin = SharedPreferencesPlugin() - let testKey = "\(aPrefix)foo" + let plugin = LegacySharedPreferencesPlugin() + let testKey = "\(aPrefix)\(testKey)" plugin.setValue(key: testKey, value: 42) // Make sure there is something to clear, so the test can't pass due to a set failure. @@ -91,19 +97,128 @@ class RunnerTests: XCTestCase { func testClearWithAllowlist() throws { for aPrefix in prefixes { - let plugin = SharedPreferencesPlugin() - let testKey = "\(aPrefix)foo" + let plugin = LegacySharedPreferencesPlugin() + let testKey = "\(aPrefix)\(testKey)" plugin.setValue(key: testKey, value: 42) // Make sure there is something to clear, so the test can't pass due to a set failure. let preRemovalValues = plugin.getAll(prefix: aPrefix, allowList: nil) XCTAssertEqual(preRemovalValues[testKey] as? Int, 42) - plugin.clear(prefix: aPrefix, allowList: ["\(aPrefix)notfoo"]) + plugin.clear(prefix: aPrefix, allowList: ["\(aPrefix)\(testKeyTwo)"]) let finalValues = plugin.getAll(prefix: aPrefix, allowList: nil) XCTAssertEqual(finalValues[testKey] as? Int, 42) } } + // Async system tests. + + let emptyOptions = SharedPreferencesPigeonOptions() + + func testAsyncSetAndGet() throws { + let plugin = SharedPreferencesPlugin() + + try plugin.set(key: "aBool", value: true, options: emptyOptions) + try plugin.set(key: "aDouble", value: 3.14, options: emptyOptions) + try plugin.set(key: "anInt", value: 42, options: emptyOptions) + try plugin.set(key: "aString", value: "hello world", options: emptyOptions) + try plugin.set(key: "aStringList", value: ["hello", "world"], options: emptyOptions) + + XCTAssertEqual(((try plugin.getValue(key: "aBool", options: emptyOptions)) != nil), true) + XCTAssertEqual( + try plugin.getValue(key: "aDouble", options: emptyOptions) as! Double, 3.14, accuracy: 0.0001) + XCTAssertEqual(try plugin.getValue(key: "anInt", options: emptyOptions) as! Int, 42) + XCTAssertEqual( + try plugin.getValue(key: "aString", options: emptyOptions) as! String, "hello world") + XCTAssertEqual( + try plugin.getValue(key: "aStringList", options: emptyOptions) as! [String], + ["hello", "world"]) + } + + func testAsyncGetAll() throws { + let plugin = SharedPreferencesPlugin() + + try plugin.set(key: "aBool", value: true, options: emptyOptions) + try plugin.set(key: "aDouble", value: 3.14, options: emptyOptions) + try plugin.set(key: "anInt", value: 42, options: emptyOptions) + try plugin.set(key: "aString", value: "hello world", options: emptyOptions) + try plugin.set(key: "aStringList", value: ["hello", "world"], options: emptyOptions) + + let storedValues = try plugin.getAll(allowList: nil, options: emptyOptions) + XCTAssertEqual(storedValues["aBool"] as? Bool, true) + XCTAssertEqual(storedValues["aDouble"] as! Double, 3.14, accuracy: 0.0001) + XCTAssertEqual(storedValues["anInt"] as? Int, 42) + XCTAssertEqual(storedValues["aString"] as? String, "hello world") + XCTAssertEqual(storedValues["aStringList"] as? [String], ["hello", "world"]) + + } + + func testAsyncGetAllWithAllowList() throws { + let plugin = SharedPreferencesPlugin() + + try plugin.set(key: "aBool", value: true, options: emptyOptions) + try plugin.set(key: "aDouble", value: 3.14, options: emptyOptions) + try plugin.set(key: "anInt", value: 42, options: emptyOptions) + try plugin.set(key: "aString", value: "hello world", options: emptyOptions) + try plugin.set(key: "aStringList", value: ["hello", "world"], options: emptyOptions) + + let storedValues = try plugin.getAll(allowList: ["aBool"], options: emptyOptions) + XCTAssertEqual(storedValues["aBool"] as? Bool, true) + XCTAssertNil(storedValues["aDouble"] ?? nil) + XCTAssertNil(storedValues["anInt"] ?? nil) + XCTAssertNil(storedValues["aString"] ?? nil) + XCTAssertNil(storedValues["aStringList"] ?? nil) + + } + + func testAsyncRemove() throws { + let plugin = SharedPreferencesPlugin() + try plugin.set(key: testKey, value: testValue, options: emptyOptions) + + // Make sure there is something to remove, so the test can't pass due to a set failure. + let preRemovalValue = try plugin.getValue(key: testKey, options: emptyOptions) as! String + XCTAssertEqual(preRemovalValue, testValue) + + // Then verify that removing it works. + try plugin.remove(key: testKey, options: emptyOptions) + + let finalValue = try plugin.getValue(key: testKey, options: emptyOptions) + XCTAssertNil(finalValue) + + } + + func testAsyncClearWithNoAllowlist() throws { + let plugin = SharedPreferencesPlugin() + try plugin.set(key: testKey, value: testValue, options: emptyOptions) + + // Make sure there is something to remove, so the test can't pass due to a set failure. + let preRemovalValue = try plugin.getValue(key: testKey, options: emptyOptions) as! String + XCTAssertEqual(preRemovalValue, testValue) + + // Then verify that clearing works. + try plugin.clear(allowList: nil, options: emptyOptions) + + let finalValue = try plugin.getValue(key: testKey, options: emptyOptions) + XCTAssertNil(finalValue) + + } + + func testAsyncClearWithAllowlist() throws { + let plugin = SharedPreferencesPlugin() + + try plugin.set(key: testKey, value: testValue, options: emptyOptions) + try plugin.set(key: testKeyTwo, value: testValue, options: emptyOptions) + + // Make sure there is something to clear, so the test can't pass due to a set failure. + let preRemovalValue = try plugin.getValue(key: testKey, options: emptyOptions) as! String + XCTAssertEqual(preRemovalValue, testValue) + + try plugin.clear(allowList: [testKey], options: emptyOptions) + + let finalValueNil = try plugin.getValue(key: testKey, options: emptyOptions) + XCTAssertNil(finalValueNil) + let finalValueNotNil = try plugin.getValue(key: testKeyTwo, options: emptyOptions) as! String + XCTAssertEqual(finalValueNotNil, testValue) + } } diff --git a/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/SharedPreferencesPlugin.swift b/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/SharedPreferencesPlugin.swift index b88562e8398..ae86be7bab2 100644 --- a/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/SharedPreferencesPlugin.swift +++ b/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/SharedPreferencesPlugin.swift @@ -10,16 +10,19 @@ import Foundation import FlutterMacOS #endif -public class SharedPreferencesPlugin: NSObject, FlutterPlugin, UserDefaultsApi { +let argumentError: String = "Argument Error" + +public class LegacySharedPreferencesPlugin: NSObject, FlutterPlugin, LegacyUserDefaultsApi { + public static func register(with registrar: FlutterPluginRegistrar) { - let instance = SharedPreferencesPlugin() + let instance = LegacySharedPreferencesPlugin() // Workaround for https://github.com/flutter/flutter/issues/118103. #if os(iOS) let messenger = registrar.messenger() #else let messenger = registrar.messenger #endif - UserDefaultsApiSetup.setUp(binaryMessenger: messenger, api: instance) + LegacyUserDefaultsApiSetup.setUp(binaryMessenger: messenger, api: instance) } func getAll(prefix: String, allowList: [String]?) -> [String?: Any?] { @@ -54,18 +57,132 @@ public class SharedPreferencesPlugin: NSObject, FlutterPlugin, UserDefaultsApi { /// If [allowList] is included, only items included will be returned. func getAllPrefs(prefix: String, allowList: [String]?) -> [String: Any] { var filteredPrefs: [String: Any] = [:] - var allowSet: Set? + + let prefs = try! SharedPreferencesPlugin.getAllPrefs( + allowList: allowList, options: SharedPreferencesPigeonOptions()) + + for (key, value) in prefs where (key.hasPrefix(prefix)) { + filteredPrefs[key] = value + } + + return filteredPrefs + } + +} + +public class SharedPreferencesPlugin: NSObject, FlutterPlugin, UserDefaultsApi { + + public static func register(with registrar: FlutterPluginRegistrar) { + let instance = SharedPreferencesPlugin() + // Workaround for https://github.com/flutter/flutter/issues/118103. + #if os(iOS) + let messenger = registrar.messenger() + #else + let messenger = registrar.messenger + #endif + UserDefaultsApiSetup.setUp(binaryMessenger: messenger, api: instance) + LegacySharedPreferencesPlugin.register(with: registrar) + } + + static private func getUserDefaults(options: SharedPreferencesPigeonOptions) throws + -> UserDefaults + { + #if os(iOS) + if !(options.suiteName?.starts(with: "group.") ?? true) { + throw FlutterError( + code: argumentError, + message: + "The provided Suite Name '\(options.suiteName!)' does not follow the predefined requirements", + details: "") as! Error + } + #endif + let prefs = UserDefaults(suiteName: options.suiteName) + + if prefs == nil { + throw FlutterError( + code: argumentError, + message: "The provided Suite Name '\(options.suiteName!)' does not exist", + details: "") as! Error + } + return prefs! + } + + func getKeys(allowList: [String]?, options: SharedPreferencesPigeonOptions) throws -> [String] { + return Array(try getAll(allowList: allowList, options: options).keys) + } + + func getAll(allowList: [String]?, options: SharedPreferencesPigeonOptions) throws -> [String: Any] + { + return try SharedPreferencesPlugin.getAllPrefs(allowList: allowList, options: options) + } + + func set(key: String, value: Any, options: SharedPreferencesPigeonOptions) throws { + try SharedPreferencesPlugin.getUserDefaults(options: options).set(value, forKey: key) + } + + func getValue(key: String, options: SharedPreferencesPigeonOptions) throws -> Any? { + let preference = try SharedPreferencesPlugin.getUserDefaults(options: options).object( + forKey: key) + return SharedPreferencesPlugin.isTypeCompatible(value: preference as Any) ? preference : nil + } + + func remove(key: String, options: SharedPreferencesPigeonOptions) throws { + try SharedPreferencesPlugin.getUserDefaults(options: options).removeObject(forKey: key) + } + + func clear(allowList: [String]?, options: SharedPreferencesPigeonOptions) throws { + let defaults = try SharedPreferencesPlugin.getUserDefaults(options: options) if let allowList = allowList { - allowSet = Set(allowList) + for (key) in allowList { + defaults.removeObject(forKey: key) + } + } else { + for key in defaults.dictionaryRepresentation().keys { + defaults.removeObject(forKey: key) + } } + } + + /// Returns all preferences stored with specified prefix. + /// If [allowList] is included, only items included will be returned. + /// If no [allowList], returns supported types only. + static func getAllPrefs(allowList: [String]?, options: SharedPreferencesPigeonOptions) throws + -> [String: Any] + { + var filteredPrefs: [String: Any] = [:] + var compatiblePrefs: [String: Any] = [:] + let allowSet = allowList.map { Set($0) } if let appDomain = Bundle.main.bundleIdentifier, - let prefs = UserDefaults.standard.persistentDomain(forName: appDomain) + let prefs = try getUserDefaults(options: options).persistentDomain(forName: appDomain) { - for (key, value) in prefs - where (key.hasPrefix(prefix) && (allowSet == nil || allowSet!.contains(key))) { - filteredPrefs[key] = value + if let allowSet = allowSet { + filteredPrefs = prefs.filter { allowSet.contains($0.key) } + } else { + filteredPrefs = prefs } + compatiblePrefs = filteredPrefs.filter { isTypeCompatible(value: $0.value) } } - return filteredPrefs + return compatiblePrefs } + + static func isTypeCompatible(value: Any) -> Bool { + switch value { + case is Bool: + return true + case is Double: + return true + case is String: + return true + case is Int: + return true + case is [Any]: + if let value = value as? [Any] { + return value.allSatisfy(isTypeCompatible) + } + default: + return false + } + return false + } + } diff --git a/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/messages.g.swift b/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/messages.g.swift index cb247f9c8cb..1c843a6522c 100644 --- a/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/messages.g.swift +++ b/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/messages.g.swift @@ -1,7 +1,7 @@ // 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 (v10.1.6), do not edit directly. +// Autogenerated from Pigeon (v16.0.5), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation @@ -33,12 +33,71 @@ private func wrapError(_ error: Any) -> [Any?] { ] } +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + private func nilOrValue(_ value: Any?) -> T? { if value is NSNull { return nil } return value as! T? } + +/// Generated class from Pigeon that represents data sent in messages. +struct SharedPreferencesPigeonOptions { + var suiteName: String? = nil + + static func fromList(_ list: [Any?]) -> SharedPreferencesPigeonOptions? { + let suiteName: String? = nilOrValue(list[0]) + + return SharedPreferencesPigeonOptions( + suiteName: suiteName + ) + } + func toList() -> [Any?] { + return [ + suiteName + ] + } +} +private class LegacyUserDefaultsApiCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 128: + return SharedPreferencesPigeonOptions.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class LegacyUserDefaultsApiCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? SharedPreferencesPigeonOptions { + super.writeByte(128) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class LegacyUserDefaultsApiCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return LegacyUserDefaultsApiCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return LegacyUserDefaultsApiCodecWriter(data: data) + } +} + +class LegacyUserDefaultsApiCodec: FlutterStandardMessageCodec { + static let shared = LegacyUserDefaultsApiCodec( + readerWriter: LegacyUserDefaultsApiCodecReaderWriter()) +} + /// Generated protocol from Pigeon that represents a handler of messages from Flutter. -protocol UserDefaultsApi { +protocol LegacyUserDefaultsApi { func remove(key: String) throws func setBool(key: String, value: Bool) throws func setDouble(key: String, value: Double) throws @@ -48,13 +107,14 @@ protocol UserDefaultsApi { } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. -class UserDefaultsApiSetup { - /// The codec used by UserDefaultsApi. - /// Sets up an instance of `UserDefaultsApi` to handle messages through the `binaryMessenger`. - static func setUp(binaryMessenger: FlutterBinaryMessenger, api: UserDefaultsApi?) { +class LegacyUserDefaultsApiSetup { + /// The codec used by LegacyUserDefaultsApi. + static var codec: FlutterStandardMessageCodec { LegacyUserDefaultsApiCodec.shared } + /// Sets up an instance of `LegacyUserDefaultsApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: LegacyUserDefaultsApi?) { let removeChannel = FlutterBasicMessageChannel( - name: "dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.remove", - binaryMessenger: binaryMessenger) + name: "dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.remove", + binaryMessenger: binaryMessenger, codec: codec) if let api = api { removeChannel.setMessageHandler { message, reply in let args = message as! [Any?] @@ -70,8 +130,8 @@ class UserDefaultsApiSetup { removeChannel.setMessageHandler(nil) } let setBoolChannel = FlutterBasicMessageChannel( - name: "dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.setBool", - binaryMessenger: binaryMessenger) + name: "dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setBool", + binaryMessenger: binaryMessenger, codec: codec) if let api = api { setBoolChannel.setMessageHandler { message, reply in let args = message as! [Any?] @@ -88,8 +148,8 @@ class UserDefaultsApiSetup { setBoolChannel.setMessageHandler(nil) } let setDoubleChannel = FlutterBasicMessageChannel( - name: "dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.setDouble", - binaryMessenger: binaryMessenger) + name: "dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setDouble", + binaryMessenger: binaryMessenger, codec: codec) if let api = api { setDoubleChannel.setMessageHandler { message, reply in let args = message as! [Any?] @@ -106,8 +166,8 @@ class UserDefaultsApiSetup { setDoubleChannel.setMessageHandler(nil) } let setValueChannel = FlutterBasicMessageChannel( - name: "dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.setValue", - binaryMessenger: binaryMessenger) + name: "dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setValue", + binaryMessenger: binaryMessenger, codec: codec) if let api = api { setValueChannel.setMessageHandler { message, reply in let args = message as! [Any?] @@ -124,8 +184,8 @@ class UserDefaultsApiSetup { setValueChannel.setMessageHandler(nil) } let getAllChannel = FlutterBasicMessageChannel( - name: "dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getAll", - binaryMessenger: binaryMessenger) + name: "dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.getAll", + binaryMessenger: binaryMessenger, codec: codec) if let api = api { getAllChannel.setMessageHandler { message, reply in let args = message as! [Any?] @@ -142,8 +202,8 @@ class UserDefaultsApiSetup { getAllChannel.setMessageHandler(nil) } let clearChannel = FlutterBasicMessageChannel( - name: "dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.clear", - binaryMessenger: binaryMessenger) + name: "dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.clear", + binaryMessenger: binaryMessenger, codec: codec) if let api = api { clearChannel.setMessageHandler { message, reply in let args = message as! [Any?] @@ -161,3 +221,157 @@ class UserDefaultsApiSetup { } } } +private class UserDefaultsApiCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 128: + return SharedPreferencesPigeonOptions.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class UserDefaultsApiCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? SharedPreferencesPigeonOptions { + super.writeByte(128) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class UserDefaultsApiCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return UserDefaultsApiCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return UserDefaultsApiCodecWriter(data: data) + } +} + +class UserDefaultsApiCodec: FlutterStandardMessageCodec { + static let shared = UserDefaultsApiCodec(readerWriter: UserDefaultsApiCodecReaderWriter()) +} + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol UserDefaultsApi { + /// Adds property to shared preferences data set of type String. + func set(key: String, value: Any, options: SharedPreferencesPigeonOptions) throws + /// Removes all properties from shared preferences data set with matching prefix. + func clear(allowList: [String]?, options: SharedPreferencesPigeonOptions) throws + /// Gets all properties from shared preferences data set with matching prefix. + func getAll(allowList: [String]?, options: SharedPreferencesPigeonOptions) throws -> [String: Any] + /// Gets individual value stored with [key], if any. + func getValue(key: String, options: SharedPreferencesPigeonOptions) throws -> Any? + /// Gets all properties from shared preferences data set with matching prefix. + func getKeys(allowList: [String]?, options: SharedPreferencesPigeonOptions) throws -> [String] +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class UserDefaultsApiSetup { + /// The codec used by UserDefaultsApi. + static var codec: FlutterStandardMessageCodec { UserDefaultsApiCodec.shared } + /// Sets up an instance of `UserDefaultsApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: UserDefaultsApi?) { + /// Adds property to shared preferences data set of type String. + let setChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.set", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + setChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let keyArg = args[0] as! String + let valueArg = args[1]! + let optionsArg = args[2] as! SharedPreferencesPigeonOptions + do { + try api.set(key: keyArg, value: valueArg, options: optionsArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + setChannel.setMessageHandler(nil) + } + /// Removes all properties from shared preferences data set with matching prefix. + let clearChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.clear", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + clearChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let allowListArg: [String]? = nilOrValue(args[0]) + let optionsArg = args[1] as! SharedPreferencesPigeonOptions + do { + try api.clear(allowList: allowListArg, options: optionsArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + clearChannel.setMessageHandler(nil) + } + /// Gets all properties from shared preferences data set with matching prefix. + let getAllChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getAll", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getAllChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let allowListArg: [String]? = nilOrValue(args[0]) + let optionsArg = args[1] as! SharedPreferencesPigeonOptions + do { + let result = try api.getAll(allowList: allowListArg, options: optionsArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getAllChannel.setMessageHandler(nil) + } + /// Gets individual value stored with [key], if any. + let getValueChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getValue", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getValueChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let keyArg = args[0] as! String + let optionsArg = args[1] as! SharedPreferencesPigeonOptions + do { + let result = try api.getValue(key: keyArg, options: optionsArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getValueChannel.setMessageHandler(nil) + } + /// Gets all properties from shared preferences data set with matching prefix. + let getKeysChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getKeys", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getKeysChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let allowListArg: [String]? = nilOrValue(args[0]) + let optionsArg = args[1] as! SharedPreferencesPigeonOptions + do { + let result = try api.getKeys(allowList: allowListArg, options: optionsArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getKeysChannel.setMessageHandler(nil) + } + } +} diff --git a/packages/shared_preferences/shared_preferences_foundation/example/integration_test/shared_preferences_test.dart b/packages/shared_preferences/shared_preferences_foundation/example/integration_test/shared_preferences_test.dart index 49892ec18aa..67777288d98 100644 --- a/packages/shared_preferences/shared_preferences_foundation/example/integration_test/shared_preferences_test.dart +++ b/packages/shared_preferences/shared_preferences_foundation/example/integration_test/shared_preferences_test.dart @@ -4,13 +4,15 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:shared_preferences_foundation/shared_preferences_foundation.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; import 'package:shared_preferences_platform_interface/types.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - group('SharedPreferencesFoundation', () { + group('SharedPreferencesAsyncFoundation', () { const Map flutterTestValues = { 'flutter.String': 'hello world', 'flutter.Bool': true, @@ -481,4 +483,199 @@ void main() { expect(values['Int'], writeCount); }); }); + + group('shared_preferences_async', () { + final SharedPreferencesAsyncFoundationOptions emptyOptions = + SharedPreferencesAsyncFoundationOptions(); + + const String stringKey = 'testString'; + const String boolKey = 'testBool'; + const String intKey = 'testInt'; + const String doubleKey = 'testDouble'; + const String listKey = 'testList'; + + const String testString = 'hello world'; + const bool testBool = true; + const int testInt = 42; + const double testDouble = 3.14159; + const List testList = ['foo', 'bar']; + + Future getPreferences() async { + final SharedPreferencesAsyncPlatform preferences = + SharedPreferencesAsyncPlatform.instance!; + await preferences.clear( + const ClearPreferencesParameters(filter: PreferencesFilters()), + emptyOptions); + return preferences; + } + + testWidgets('set and get String', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setString(stringKey, testString, emptyOptions); + expect(await preferences.getString(stringKey, emptyOptions), testString); + }); + + testWidgets('set and get bool', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setBool(boolKey, testBool, emptyOptions); + expect(await preferences.getBool(boolKey, emptyOptions), testBool); + }); + + testWidgets('set and get int', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setInt(intKey, testInt, emptyOptions); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + }); + + testWidgets('set and get double', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + }); + + testWidgets('set and get StringList', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setStringList(listKey, testList, emptyOptions); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); + + testWidgets('getPreferences', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, + ); + + expect(gotAll.length, 5); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + expect(gotAll[intKey], testInt); + expect(gotAll[doubleKey], testDouble); + expect(gotAll[listKey], testList); + }); + + testWidgets('getPreferences with filter', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + + expect(gotAll.length, 2); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + }); + + testWidgets('getKeys', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, + ); + + expect(keys.length, 5); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + expect(keys, contains(intKey)); + expect(keys, contains(doubleKey)); + expect(keys, contains(listKey)); + }); + + testWidgets('getKeys with filter', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + + expect(keys.length, 2); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + }); + + testWidgets('clear', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + await preferences.clear( + const ClearPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, + ); + + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), null); + expect(await preferences.getDouble(doubleKey, emptyOptions), null); + expect(await preferences.getStringList(listKey, emptyOptions), null); + }); + + testWidgets('clear with filter', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + await preferences.clear( + const ClearPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); + }); } diff --git a/packages/shared_preferences/shared_preferences_foundation/example/lib/main.dart b/packages/shared_preferences/shared_preferences_foundation/example/lib/main.dart index e7cb57b7eb6..6623eb653f1 100644 --- a/packages/shared_preferences/shared_preferences_foundation/example/lib/main.dart +++ b/packages/shared_preferences/shared_preferences_foundation/example/lib/main.dart @@ -4,10 +4,9 @@ // ignore_for_file: public_member_api_docs -import 'dart:async'; - import 'package:flutter/material.dart'; -import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; +import 'package:shared_preferences_foundation/shared_preferences_foundation.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; void main() { runApp(const MyApp()); @@ -33,32 +32,36 @@ class SharedPreferencesDemo extends StatefulWidget { } class SharedPreferencesDemoState extends State { - final SharedPreferencesStorePlatform _prefs = - SharedPreferencesStorePlatform.instance; + final SharedPreferencesAsyncPlatform? _prefs = + SharedPreferencesAsyncPlatform.instance; + SharedPreferencesAsyncFoundationOptions options = + SharedPreferencesAsyncFoundationOptions(); + static const String _counterKey = 'counter'; late Future _counter; - // Includes the prefix because this is using the platform interface directly, - // but the prefix (which the native code assumes is present) is added by the - // app-facing package. - static const String _prefKey = 'flutter.counter'; - Future _incrementCounter() async { - final Map values = await _prefs.getAll(); - final int counter = ((values[_prefKey] as int?) ?? 0) + 1; + final int? value = await _prefs!.getInt(_counterKey, options); + final int counter = (value ?? 0) + 1; setState(() { - _counter = _prefs.setValue('Int', _prefKey, counter).then((bool success) { + _counter = _prefs.setInt(_counterKey, counter, options).then((_) { return counter; }); }); } + Future _getAndSetCounter() async { + setState(() { + _counter = _prefs!.getInt(_counterKey, options).then((int? counter) { + return counter ?? 0; + }); + }); + } + @override void initState() { super.initState(); - _counter = _prefs.getAll().then((Map values) { - return (values[_prefKey] as int?) ?? 0; - }); + _getAndSetCounter(); } @override diff --git a/packages/shared_preferences/shared_preferences_foundation/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_foundation/example/pubspec.yaml index 39df1eb0f46..dfee764e631 100644 --- a/packages/shared_preferences/shared_preferences_foundation/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_foundation/example/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - shared_preferences_platform_interface: ^2.3.0 + shared_preferences_platform_interface: ^2.4.0 dev_dependencies: flutter_test: diff --git a/packages/shared_preferences/shared_preferences_foundation/lib/messages.g.dart b/packages/shared_preferences/shared_preferences_foundation/lib/messages.g.dart deleted file mode 100644 index 132d92ac224..00000000000 --- a/packages/shared_preferences/shared_preferences_foundation/lib/messages.g.dart +++ /dev/null @@ -1,172 +0,0 @@ -// 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 (v10.1.6), 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 UserDefaultsApi { - /// Constructor for [UserDefaultsApi]. 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. - UserDefaultsApi({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.shared_preferences_foundation.UserDefaultsApi.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 { - return; - } - } - - Future setBool(String arg_key, bool arg_value) async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.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 { - return; - } - } - - Future setDouble(String arg_key, double arg_value) async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.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 { - return; - } - } - - Future setValue(String arg_key, Object arg_value) async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.setValue', - 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 { - return; - } - } - - Future> getAll( - String arg_prefix, List? arg_allowList) async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getAll', - codec, - binaryMessenger: _binaryMessenger); - final List? replyList = await channel - .send([arg_prefix, arg_allowList]) 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(); - } - } - - Future clear(String arg_prefix, List? arg_allowList) async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.clear', - codec, - binaryMessenger: _binaryMessenger); - final List? replyList = await channel - .send([arg_prefix, arg_allowList]) 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?)!; - } - } -} diff --git a/packages/shared_preferences/shared_preferences_foundation/lib/shared_preferences_foundation.dart b/packages/shared_preferences/shared_preferences_foundation/lib/shared_preferences_foundation.dart index 92699b7fbfa..43234666b9c 100644 --- a/packages/shared_preferences/shared_preferences_foundation/lib/shared_preferences_foundation.dart +++ b/packages/shared_preferences/shared_preferences_foundation/lib/shared_preferences_foundation.dart @@ -2,106 +2,5 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/services.dart'; -import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; -import 'package:shared_preferences_platform_interface/types.dart'; -import 'messages.g.dart'; - -typedef _Setter = Future Function(String key, Object value); - -/// iOS and macOS implementation of shared_preferences. -class SharedPreferencesFoundation extends SharedPreferencesStorePlatform { - final UserDefaultsApi _api = UserDefaultsApi(); - - static const String _defaultPrefix = 'flutter.'; - - late final Map _setters = { - 'Bool': (String key, Object value) { - return _api.setBool(key, value as bool); - }, - 'Double': (String key, Object value) { - return _api.setDouble(key, value as double); - }, - 'Int': (String key, Object value) { - return _api.setValue(key, value as int); - }, - 'String': (String key, Object value) { - return _api.setValue(key, value as String); - }, - 'StringList': (String key, Object value) { - return _api.setValue(key, value as List); - }, - }; - - /// Registers this class as the default instance of - /// [SharedPreferencesStorePlatform]. - static void registerWith() { - SharedPreferencesStorePlatform.instance = SharedPreferencesFoundation(); - } - - @override - Future clear() async { - return clearWithParameters( - ClearParameters( - filter: PreferencesFilter(prefix: _defaultPrefix), - ), - ); - } - - @override - Future clearWithPrefix(String prefix) async { - return clearWithParameters( - ClearParameters(filter: PreferencesFilter(prefix: prefix))); - } - - @override - Future clearWithParameters(ClearParameters parameters) async { - final PreferencesFilter filter = parameters.filter; - return _api.clear( - filter.prefix, - filter.allowList?.toList(), - ); - } - - @override - Future> getAll() async { - return getAllWithParameters( - GetAllParameters( - filter: PreferencesFilter(prefix: _defaultPrefix), - ), - ); - } - - @override - Future> getAllWithPrefix(String prefix) async { - return getAllWithParameters( - GetAllParameters(filter: PreferencesFilter(prefix: prefix))); - } - - @override - Future> getAllWithParameters( - GetAllParameters parameters) async { - final PreferencesFilter filter = parameters.filter; - final Map data = - await _api.getAll(filter.prefix, filter.allowList?.toList()); - return data.cast(); - } - - @override - Future remove(String key) async { - await _api.remove(key); - return true; - } - - @override - Future setValue(String valueType, String key, Object value) async { - final _Setter? setter = _setters[valueType]; - if (setter == null) { - throw PlatformException( - code: 'InvalidOperation', - message: '"$valueType" is not a supported type.'); - } - await setter(key, value); - return true; - } -} +export 'src/shared_preferences_async_foundation.dart'; +export 'src/shared_preferences_foundation.dart'; diff --git a/packages/shared_preferences/shared_preferences_foundation/lib/src/messages.g.dart b/packages/shared_preferences/shared_preferences_foundation/lib/src/messages.g.dart new file mode 100644 index 00000000000..6594deec3bb --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/lib/src/messages.g.dart @@ -0,0 +1,418 @@ +// 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 (v16.0.5), 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, no_leading_underscores_for_local_identifiers + +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'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +List wrapResponse( + {Object? result, PlatformException? error, bool empty = false}) { + if (empty) { + return []; + } + if (error == null) { + return [result]; + } + return [error.code, error.message, error.details]; +} + +class SharedPreferencesPigeonOptions { + SharedPreferencesPigeonOptions({ + this.suiteName, + }); + + String? suiteName; + + Object encode() { + return [ + suiteName, + ]; + } + + static SharedPreferencesPigeonOptions decode(Object result) { + result as List; + return SharedPreferencesPigeonOptions( + suiteName: result[0] as String?, + ); + } +} + +class _LegacyUserDefaultsApiCodec extends StandardMessageCodec { + const _LegacyUserDefaultsApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is SharedPreferencesPigeonOptions) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return SharedPreferencesPigeonOptions.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class LegacyUserDefaultsApi { + /// Constructor for [LegacyUserDefaultsApi]. 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. + LegacyUserDefaultsApi({BinaryMessenger? binaryMessenger}) + : __pigeon_binaryMessenger = binaryMessenger; + final BinaryMessenger? __pigeon_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = + _LegacyUserDefaultsApiCodec(); + + Future remove(String key) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.remove'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([key]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + Future setBool(String key, bool value) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setBool'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([key, value]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + Future setDouble(String key, double value) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setDouble'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([key, value]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + Future setValue(String key, Object value) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setValue'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([key, value]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + Future> getAll( + String prefix, List? allowList) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.getAll'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([prefix, allowList]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as Map?)! + .cast(); + } + } + + Future clear(String prefix, List? allowList) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.clear'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([prefix, allowList]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } +} + +class _UserDefaultsApiCodec extends StandardMessageCodec { + const _UserDefaultsApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is SharedPreferencesPigeonOptions) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return SharedPreferencesPigeonOptions.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class UserDefaultsApi { + /// Constructor for [UserDefaultsApi]. 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. + UserDefaultsApi({BinaryMessenger? binaryMessenger}) + : __pigeon_binaryMessenger = binaryMessenger; + final BinaryMessenger? __pigeon_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = + _UserDefaultsApiCodec(); + + /// Adds property to shared preferences data set of type String. + Future set( + String key, Object value, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.set'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([key, value, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Removes all properties from shared preferences data set with matching prefix. + Future clear( + List? allowList, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.clear'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([allowList, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Gets all properties from shared preferences data set with matching prefix. + Future> getAll( + List? allowList, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getAll'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([allowList, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as Map?)! + .cast(); + } + } + + /// Gets individual value stored with [key], if any. + Future getValue( + String key, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getValue'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([key, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return __pigeon_replyList[0]; + } + } + + /// Gets all properties from shared preferences data set with matching prefix. + Future> getKeys( + List? allowList, SharedPreferencesPigeonOptions options) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getKeys'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([allowList, options]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as List?)!.cast(); + } + } +} diff --git a/packages/shared_preferences/shared_preferences_foundation/lib/src/shared_preferences_async_foundation.dart b/packages/shared_preferences/shared_preferences_foundation/lib/src/shared_preferences_async_foundation.dart new file mode 100644 index 00000000000..3bf3bc24149 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/lib/src/shared_preferences_async_foundation.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. + +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; +import 'package:shared_preferences_platform_interface/types.dart'; + +import './messages.g.dart'; + +const String _argumentErrorCode = 'Argument Error'; + +/// iOS and macOS implementation of shared_preferences. +base class SharedPreferencesAsyncFoundation + extends SharedPreferencesAsyncPlatform { + /// Creates a new plugin implementation instance. + SharedPreferencesAsyncFoundation({ + @visibleForTesting UserDefaultsApi? api, + }) : _api = api ?? UserDefaultsApi(); + + final UserDefaultsApi _api; + + /// Registers this class as the default instance of [SharedPreferencesAsyncPlatform]. + static void registerWith() { + SharedPreferencesAsyncPlatform.instance = + SharedPreferencesAsyncFoundation(); + } + + /// Returns a SharedPreferencesPigeonOptions for sending to platform. + SharedPreferencesPigeonOptions _convertOptionsToPigeonOptions( + SharedPreferencesOptions options) { + if (options is SharedPreferencesAsyncFoundationOptions) { + final String? suiteName = options.suiteName; + return SharedPreferencesPigeonOptions( + suiteName: suiteName, + ); + } + return SharedPreferencesPigeonOptions(); + } + + @override + Future> getKeys( + GetPreferencesParameters parameters, + SharedPreferencesOptions options, + ) async { + final PreferencesFilters filter = parameters.filter; + // TODO(tarrinneal): Remove cast once https://github.com/flutter/flutter/issues/97848 + // is fixed. In practice, the values will never be null, and the native implementation assumes that. + return (await _convertKnownExceptions>( + () async => (await _api.getKeys( + filter.allowList?.toList(), + _convertOptionsToPigeonOptions(options), + )) + .cast()))! + .toSet(); + } + + Future _setValue( + String key, + Object value, + SharedPreferencesOptions options, + ) async { + return _convertKnownExceptions(() async => + _api.set(key, value, _convertOptionsToPigeonOptions(options))); + } + + @override + Future setString( + String key, + String value, + SharedPreferencesOptions options, + ) async { + await _setValue(key, value, options); + } + + @override + Future setInt( + String key, + int value, + SharedPreferencesOptions options, + ) async { + await _setValue(key, value, options); + } + + @override + Future setStringList( + String key, + List value, + SharedPreferencesOptions options, + ) async { + await _setValue(key, value, options); + } + + @override + Future setBool( + String key, + bool value, + SharedPreferencesOptions options, + ) async { + await _api.set(key, value, _convertOptionsToPigeonOptions(options)); + } + + @override + Future setDouble( + String key, + double value, + SharedPreferencesOptions options, + ) async { + await _api.set(key, value, _convertOptionsToPigeonOptions(options)); + } + + @override + Future getString( + String key, + SharedPreferencesOptions options, + ) async { + return _convertKnownExceptions(() async => (await _api.getValue( + key, _convertOptionsToPigeonOptions(options))) as String?); + } + + @override + Future getBool( + String key, + SharedPreferencesOptions options, + ) async { + return _convertKnownExceptions(() async => await _api.getValue( + key, _convertOptionsToPigeonOptions(options)) as bool?); + } + + @override + Future getDouble( + String key, + SharedPreferencesOptions options, + ) async { + return _convertKnownExceptions(() async => await _api.getValue( + key, _convertOptionsToPigeonOptions(options)) as double?); + } + + @override + Future getInt( + String key, + SharedPreferencesOptions options, + ) async { + return _convertKnownExceptions(() async => await _api.getValue( + key, _convertOptionsToPigeonOptions(options)) as int?); + } + + @override + Future?> getStringList( + String key, + SharedPreferencesOptions options, + ) async { + // TODO(tarrinneal): Remove cast once https://github.com/flutter/flutter/issues/97848 + // is fixed. In practice, the values will never be null, and the native implementation assumes that. + return _convertKnownExceptions>(() async => + ((await _api.getValue(key, _convertOptionsToPigeonOptions(options))) + as List?) + ?.cast()); + } + + @override + Future clear( + ClearPreferencesParameters parameters, + SharedPreferencesOptions options, + ) async { + final PreferencesFilters filter = parameters.filter; + return _convertKnownExceptions(() async => _api.clear( + filter.allowList?.toList(), + _convertOptionsToPigeonOptions(options), + )); + } + + @override + Future> getPreferences( + GetPreferencesParameters parameters, + SharedPreferencesOptions options, + ) async { + final PreferencesFilters filter = parameters.filter; + final Map? data = + await _convertKnownExceptions>( + () async => _api.getAll( + filter.allowList?.toList(), + _convertOptionsToPigeonOptions(options), + )); + + return data!.cast(); + } + + Future _convertKnownExceptions(Future Function() method) async { + try { + final T? value = await method(); + return value; + } on PlatformException catch (e) { + if (e.code == _argumentErrorCode) { + throw ArgumentError( + 'shared_preferences_foundation argument error ${e.message ?? ''}'); + } else { + rethrow; + } + } + } +} + +/// Options for the Foundation specific SharedPreferences plugin. +@immutable +class SharedPreferencesAsyncFoundationOptions extends SharedPreferencesOptions { + /// Creates a new instance with the given options. + SharedPreferencesAsyncFoundationOptions({ + this.suiteName, + }) { + // Ensure that use of suite is compliant with required reason API category 1C8F.1; see + // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api + if (Platform.isIOS && !(suiteName?.startsWith('group.') ?? true)) { + throw ArgumentError('iOS suite name must begin with "group."'); + } + } + + /// Name of Foundation suite to get/set to. + /// + /// On iOS this represents a container ID which must begin with `group.` + /// followed by a custom string in reverse DNS notation. + /// + /// If this option is not set, the default NSUserDefaults will be used. + final String? suiteName; + + /// Returns a new instance of [SharedPreferencesAsyncFoundationOptions] from an existing + /// [SharedPreferencesOptions]. + static SharedPreferencesAsyncFoundationOptions fromSharedPreferencesOptions( + SharedPreferencesOptions options) { + if (options is SharedPreferencesAsyncFoundationOptions) { + return options; + } + return SharedPreferencesAsyncFoundationOptions(); + } +} diff --git a/packages/shared_preferences/shared_preferences_foundation/lib/src/shared_preferences_foundation.dart b/packages/shared_preferences/shared_preferences_foundation/lib/src/shared_preferences_foundation.dart new file mode 100644 index 00000000000..b68b6308c74 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/lib/src/shared_preferences_foundation.dart @@ -0,0 +1,110 @@ +// 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:flutter/services.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; +import 'package:shared_preferences_platform_interface/types.dart'; +import './messages.g.dart'; +import 'shared_preferences_async_foundation.dart'; + +typedef _Setter = Future Function(String key, Object value); + +/// iOS and macOS implementation of shared_preferences. +class SharedPreferencesFoundation extends SharedPreferencesStorePlatform { + final LegacyUserDefaultsApi _api = LegacyUserDefaultsApi(); + + static const String _defaultPrefix = 'flutter.'; + + late final Map _setters = { + 'Bool': (String key, Object value) { + return _api.setBool(key, value as bool); + }, + 'Double': (String key, Object value) { + return _api.setDouble(key, value as double); + }, + 'Int': (String key, Object value) { + return _api.setValue(key, value as int); + }, + 'String': (String key, Object value) { + return _api.setValue(key, value as String); + }, + 'StringList': (String key, Object value) { + return _api.setValue(key, value as List); + }, + }; + + /// Registers this class as the default instance of + /// [SharedPreferencesStorePlatform]. + static void registerWith() { + SharedPreferencesStorePlatform.instance = SharedPreferencesFoundation(); + // A temporary work-around for having two plugins contained in a single package. + SharedPreferencesAsyncFoundation.registerWith(); + } + + @override + Future clear() async { + return clearWithParameters( + ClearParameters( + filter: PreferencesFilter(prefix: _defaultPrefix), + ), + ); + } + + @override + Future clearWithPrefix(String prefix) async { + return clearWithParameters( + ClearParameters(filter: PreferencesFilter(prefix: prefix))); + } + + @override + Future clearWithParameters(ClearParameters parameters) async { + final PreferencesFilter filter = parameters.filter; + return _api.clear( + filter.prefix, + filter.allowList?.toList(), + ); + } + + @override + Future> getAll() async { + return getAllWithParameters( + GetAllParameters( + filter: PreferencesFilter(prefix: _defaultPrefix), + ), + ); + } + + @override + Future> getAllWithPrefix(String prefix) async { + return getAllWithParameters( + GetAllParameters(filter: PreferencesFilter(prefix: prefix))); + } + + @override + Future> getAllWithParameters( + GetAllParameters parameters) async { + final PreferencesFilter filter = parameters.filter; + final Map data = + await _api.getAll(filter.prefix, filter.allowList?.toList()); + return data.cast(); + } + + @override + Future remove(String key) async { + await _api.remove(key); + return true; + } + + @override + Future setValue(String valueType, String key, Object value) async { + final _Setter? setter = _setters[valueType]; + if (setter == null) { + throw PlatformException( + code: 'InvalidOperation', + message: '"$valueType" is not a supported type.'); + } + await setter(key, value); + return true; + } +} diff --git a/packages/shared_preferences/shared_preferences_foundation/pigeons/messages.dart b/packages/shared_preferences/shared_preferences_foundation/pigeons/messages.dart index ad8060e3474..96430e55662 100644 --- a/packages/shared_preferences/shared_preferences_foundation/pigeons/messages.dart +++ b/packages/shared_preferences/shared_preferences_foundation/pigeons/messages.dart @@ -5,17 +5,15 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( - dartOut: 'lib/messages.g.dart', + dartOut: 'lib/src/messages.g.dart', dartTestOut: 'test/test_api.g.dart', swiftOut: 'darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/messages.g.swift', copyrightHeader: 'pigeons/copyright_header.txt', )) @HostApi(dartHostTestHandler: 'TestUserDefaultsApi') -abstract class UserDefaultsApi { +abstract class LegacyUserDefaultsApi { void remove(String key); - // TODO(stuartmorgan): Give these setters better Swift signatures (_,forKey:) - // once https://github.com/flutter/flutter/issues/105932 is fixed. void setBool(String key, bool value); void setDouble(String key, double value); void setValue(String key, Object value); @@ -24,3 +22,45 @@ abstract class UserDefaultsApi { Map getAll(String prefix, List? allowList); bool clear(String prefix, List? allowList); } + +class SharedPreferencesPigeonOptions { + SharedPreferencesPigeonOptions({ + this.suiteName, + }); + String? suiteName; +} + +@HostApi(dartHostTestHandler: 'TestSharedPreferencesAsyncApi') +abstract class UserDefaultsApi { + /// Adds property to shared preferences data set of type String. + @SwiftFunction('set(key:value:options:)') + void set( + String key, + Object value, + SharedPreferencesPigeonOptions options, + ); + + /// Removes all properties from shared preferences data set with matching prefix. + void clear( + List? allowList, + SharedPreferencesPigeonOptions options, + ); + + /// Gets all properties from shared preferences data set with matching prefix. + Map getAll( + List? allowList, + SharedPreferencesPigeonOptions options, + ); + + /// Gets individual value stored with [key], if any. + Object? getValue( + String key, + SharedPreferencesPigeonOptions options, + ); + + /// Gets all properties from shared preferences data set with matching prefix. + List getKeys( + List? allowList, + SharedPreferencesPigeonOptions options, + ); +} diff --git a/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml b/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml index 2e5d728e142..53dfa7294e2 100644 --- a/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml @@ -2,7 +2,7 @@ name: shared_preferences_foundation description: iOS and macOS implementation of the shared_preferences plugin. repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_foundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.4.0 +version: 2.5.0 environment: sdk: ^3.2.3 @@ -24,12 +24,12 @@ flutter: dependencies: flutter: sdk: flutter - shared_preferences_platform_interface: ^2.3.0 + shared_preferences_platform_interface: ^2.4.0 dev_dependencies: flutter_test: sdk: flutter - pigeon: ^10.1.6 + pigeon: ^16.0.4 topics: - persistence diff --git a/packages/shared_preferences/shared_preferences_foundation/test/shared_preferences_async_foundation_test.dart b/packages/shared_preferences/shared_preferences_foundation/test/shared_preferences_async_foundation_test.dart new file mode 100644 index 00000000000..74278d6b344 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/test/shared_preferences_async_foundation_test.dart @@ -0,0 +1,247 @@ +// 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:flutter_test/flutter_test.dart'; +import 'package:shared_preferences_foundation/shared_preferences_foundation.dart'; +import 'package:shared_preferences_foundation/src/messages.g.dart'; +import 'package:shared_preferences_platform_interface/types.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const String stringKey = 'testString'; + const String boolKey = 'testBool'; + const String intKey = 'testInt'; + const String doubleKey = 'testDouble'; + const String listKey = 'testList'; + + const String testString = 'hello world'; + const bool testBool = true; + const int testInt = 42; + const double testDouble = 3.14159; + const List testList = ['foo', 'bar']; + + final SharedPreferencesAsyncFoundationOptions emptyOptions = + SharedPreferencesAsyncFoundationOptions(); + + SharedPreferencesAsyncFoundation getPreferences() { + final _FakeSharedPreferencesApi api = _FakeSharedPreferencesApi(); + final SharedPreferencesAsyncFoundation preferences = + SharedPreferencesAsyncFoundation(api: api); + + return preferences; + } + + test('set and get String', () async { + final SharedPreferencesAsyncFoundation preferences = getPreferences(); + + await preferences.setString(stringKey, testString, emptyOptions); + expect(await preferences.getString(stringKey, emptyOptions), testString); + }); + + test('set and get bool', () async { + final SharedPreferencesAsyncFoundation preferences = getPreferences(); + + await preferences.setBool(boolKey, testBool, emptyOptions); + expect(await preferences.getBool(boolKey, emptyOptions), testBool); + }); + + test('set and get int', () async { + final SharedPreferencesAsyncFoundation preferences = getPreferences(); + + await preferences.setInt(intKey, testInt, emptyOptions); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + }); + + test('set and get double', () async { + final SharedPreferencesAsyncFoundation preferences = getPreferences(); + + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + }); + + test('set and get StringList', () async { + final SharedPreferencesAsyncFoundation preferences = getPreferences(); + + await preferences.setStringList(listKey, testList, emptyOptions); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); + + test('getPreferences', () async { + final SharedPreferencesAsyncFoundation preferences = getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions); + + expect(gotAll.length, 5); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + expect(gotAll[intKey], testInt); + expect(gotAll[doubleKey], testDouble); + expect(gotAll[listKey], testList); + }); + + test('getPreferences with filter', () async { + final SharedPreferencesAsyncFoundation preferences = getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters( + filter: + PreferencesFilters(allowList: {stringKey, boolKey})), + emptyOptions); + + expect(gotAll.length, 2); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + }); + + test('getKeys', () async { + final SharedPreferencesAsyncFoundation preferences = getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, + ); + + expect(keys.length, 5); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + expect(keys, contains(intKey)); + expect(keys, contains(doubleKey)); + expect(keys, contains(listKey)); + }); + + test('getKeys with filter', () async { + final SharedPreferencesAsyncFoundation preferences = getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + + expect(keys.length, 2); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + }); + + test('clear', () async { + final SharedPreferencesAsyncFoundation preferences = getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + await preferences.clear( + const ClearPreferencesParameters(filter: PreferencesFilters()), + emptyOptions); + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), null); + expect(await preferences.getDouble(doubleKey, emptyOptions), null); + expect(await preferences.getStringList(listKey, emptyOptions), null); + }); + + test('clear with filter', () async { + final SharedPreferencesAsyncFoundation preferences = getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + await preferences.clear( + const ClearPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); +} + +class _FakeSharedPreferencesApi implements UserDefaultsApi { + final Map items = {}; + + @override + Future clear( + List? allowList, SharedPreferencesPigeonOptions options) async { + if (allowList != null) { + items.removeWhere((String key, _) => allowList.contains(key)); + } else { + items.clear(); + } + + return true; + } + + @override + Future> getAll( + List? allowList, SharedPreferencesPigeonOptions options) async { + final Map filteredItems = {...items}; + if (allowList != null) { + filteredItems.removeWhere((String key, _) => !allowList.contains(key)); + } + return filteredItems; + } + + @override + Future> getKeys( + List? allowList, SharedPreferencesPigeonOptions options) async { + final List filteredItems = items.keys.toList(); + if (allowList != null) { + filteredItems.removeWhere((String key) => !allowList.contains(key)); + } + return filteredItems; + } + + @override + Future set( + String key, Object value, SharedPreferencesPigeonOptions options) async { + items[key] = value; + } + + @override + Future getValue( + String key, SharedPreferencesPigeonOptions options) async { + return items[key]; + } +} diff --git a/packages/shared_preferences/shared_preferences_foundation/test/shared_preferences_foundation_test.dart b/packages/shared_preferences/shared_preferences_foundation/test/shared_preferences_foundation_test.dart index 496cdd9b0f7..f733122e7ad 100644 --- a/packages/shared_preferences/shared_preferences_foundation/test/shared_preferences_foundation_test.dart +++ b/packages/shared_preferences/shared_preferences_foundation/test/shared_preferences_foundation_test.dart @@ -4,7 +4,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences_foundation/shared_preferences_foundation.dart'; +import 'package:shared_preferences_foundation/src/shared_preferences_foundation.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; import 'package:shared_preferences_platform_interface/types.dart'; @@ -101,7 +101,7 @@ void main() { TestUserDefaultsApi.setup(api); }); - test('registerWith', () { + test('registerWith', () async { SharedPreferencesFoundation.registerWith(); expect(SharedPreferencesStorePlatform.instance, isA()); @@ -261,7 +261,7 @@ void main() { expect(api.items['flutter.StringList'], ['hi']); }); - test('setValue with unsupported type', () { + test('setValue with unsupported type', () async { final SharedPreferencesFoundation plugin = SharedPreferencesFoundation(); expect(() async { await plugin.setValue('Map', 'flutter.key', {}); diff --git a/packages/shared_preferences/shared_preferences_foundation/test/test_api.g.dart b/packages/shared_preferences/shared_preferences_foundation/test/test_api.g.dart index 39f8b4fede5..25617165035 100644 --- a/packages/shared_preferences/shared_preferences_foundation/test/test_api.g.dart +++ b/packages/shared_preferences/shared_preferences_foundation/test/test_api.g.dart @@ -1,9 +1,9 @@ // 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 (v10.1.6), do not edit directly. +// Autogenerated from Pigeon (v16.0.5), 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: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers // ignore_for_file: avoid_relative_lib_imports import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; @@ -11,12 +11,36 @@ import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences_foundation/messages.g.dart'; +import 'package:shared_preferences_foundation/src/messages.g.dart'; + +class _TestUserDefaultsApiCodec extends StandardMessageCodec { + const _TestUserDefaultsApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is SharedPreferencesPigeonOptions) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return SharedPreferencesPigeonOptions.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} abstract class TestUserDefaultsApi { static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => TestDefaultBinaryMessengerBinding.instance; - static const MessageCodec codec = StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = + _TestUserDefaultsApiCodec(); void remove(String key); @@ -33,154 +57,430 @@ abstract class TestUserDefaultsApi { static void setup(TestUserDefaultsApi? api, {BinaryMessenger? binaryMessenger}) { { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.remove', - codec, + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.remove', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, null); + .setMockDecodedMessageHandler(__pigeon_channel, null); } else { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, + .setMockDecodedMessageHandler(__pigeon_channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.remove was null.'); + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.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.shared_preferences_foundation.UserDefaultsApi.remove was null, expected non-null String.'); - api.remove(arg_key!); - return []; + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.remove was null, expected non-null String.'); + try { + api.remove(arg_key!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.setBool', - codec, + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setBool', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, null); + .setMockDecodedMessageHandler(__pigeon_channel, null); } else { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, + .setMockDecodedMessageHandler(__pigeon_channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.setBool was null.'); + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.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.shared_preferences_foundation.UserDefaultsApi.setBool was null, expected non-null String.'); + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setBool was null, expected non-null String.'); final bool? arg_value = (args[1] as bool?); assert(arg_value != null, - 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.setBool was null, expected non-null bool.'); - api.setBool(arg_key!, arg_value!); - return []; + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setBool was null, expected non-null bool.'); + try { + api.setBool(arg_key!, arg_value!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.setDouble', - codec, + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setDouble', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, null); + .setMockDecodedMessageHandler(__pigeon_channel, null); } else { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, + .setMockDecodedMessageHandler(__pigeon_channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.setDouble was null.'); + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.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.shared_preferences_foundation.UserDefaultsApi.setDouble was null, expected non-null String.'); + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setDouble was null, expected non-null String.'); final double? arg_value = (args[1] as double?); assert(arg_value != null, - 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.setDouble was null, expected non-null double.'); - api.setDouble(arg_key!, arg_value!); - return []; + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setDouble was null, expected non-null double.'); + try { + api.setDouble(arg_key!, arg_value!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.setValue', - codec, + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setValue', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, null); + .setMockDecodedMessageHandler(__pigeon_channel, null); } else { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, + .setMockDecodedMessageHandler(__pigeon_channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.setValue was null.'); + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setValue 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.shared_preferences_foundation.UserDefaultsApi.setValue was null, expected non-null String.'); + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setValue was null, expected non-null String.'); final Object? arg_value = (args[1] as Object?); assert(arg_value != null, - 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.setValue was null, expected non-null Object.'); - api.setValue(arg_key!, arg_value!); - return []; + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setValue was null, expected non-null Object.'); + try { + api.setValue(arg_key!, arg_value!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getAll', - codec, + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.getAll', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, null); + .setMockDecodedMessageHandler(__pigeon_channel, null); } else { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, + .setMockDecodedMessageHandler(__pigeon_channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getAll was null.'); + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.getAll was null.'); final List args = (message as List?)!; final String? arg_prefix = (args[0] as String?); assert(arg_prefix != null, - 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getAll was null, expected non-null String.'); + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.getAll was null, expected non-null String.'); final List? arg_allowList = (args[1] as List?)?.cast(); - final Map output = - api.getAll(arg_prefix!, arg_allowList); - return [output]; + try { + final Map output = + api.getAll(arg_prefix!, arg_allowList); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.clear', - codec, + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.clear', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, null); + .setMockDecodedMessageHandler(__pigeon_channel, null); } else { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, + .setMockDecodedMessageHandler(__pigeon_channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.clear was null.'); + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.clear was null.'); final List args = (message as List?)!; final String? arg_prefix = (args[0] as String?); assert(arg_prefix != null, - 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.clear was null, expected non-null String.'); + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.clear was null, expected non-null String.'); final List? arg_allowList = (args[1] as List?)?.cast(); - final bool output = api.clear(arg_prefix!, arg_allowList); - return [output]; + try { + final bool output = api.clear(arg_prefix!, arg_allowList); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } +} + +class _TestSharedPreferencesAsyncApiCodec extends StandardMessageCodec { + const _TestSharedPreferencesAsyncApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is SharedPreferencesPigeonOptions) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return SharedPreferencesPigeonOptions.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +abstract class TestSharedPreferencesAsyncApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec pigeonChannelCodec = + _TestSharedPreferencesAsyncApiCodec(); + + /// Adds property to shared preferences data set of type String. + void set(String key, Object value, SharedPreferencesPigeonOptions options); + + /// Removes all properties from shared preferences data set with matching prefix. + void clear(List? allowList, SharedPreferencesPigeonOptions options); + + /// Gets all properties from shared preferences data set with matching prefix. + Map getAll( + List? allowList, SharedPreferencesPigeonOptions options); + + /// Gets individual value stored with [key], if any. + Object? getValue(String key, SharedPreferencesPigeonOptions options); + + /// Gets all properties from shared preferences data set with matching prefix. + List getKeys( + List? allowList, SharedPreferencesPigeonOptions options); + + static void setup(TestSharedPreferencesAsyncApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.set', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.set 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.shared_preferences_foundation.UserDefaultsApi.set was null, expected non-null String.'); + final Object? arg_value = (args[1] as Object?); + assert(arg_value != null, + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.set was null, expected non-null Object.'); + final SharedPreferencesPigeonOptions? arg_options = + (args[2] as SharedPreferencesPigeonOptions?); + assert(arg_options != null, + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.set was null, expected non-null SharedPreferencesPigeonOptions.'); + try { + api.set(arg_key!, arg_value!, arg_options!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.clear', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.clear was null.'); + final List args = (message as List?)!; + final List? arg_allowList = + (args[0] as List?)?.cast(); + final SharedPreferencesPigeonOptions? arg_options = + (args[1] as SharedPreferencesPigeonOptions?); + assert(arg_options != null, + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.clear was null, expected non-null SharedPreferencesPigeonOptions.'); + try { + api.clear(arg_allowList, arg_options!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getAll', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getAll was null.'); + final List args = (message as List?)!; + final List? arg_allowList = + (args[0] as List?)?.cast(); + final SharedPreferencesPigeonOptions? arg_options = + (args[1] as SharedPreferencesPigeonOptions?); + assert(arg_options != null, + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getAll was null, expected non-null SharedPreferencesPigeonOptions.'); + try { + final Map output = + api.getAll(arg_allowList, arg_options!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getValue', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getValue 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.shared_preferences_foundation.UserDefaultsApi.getValue was null, expected non-null String.'); + final SharedPreferencesPigeonOptions? arg_options = + (args[1] as SharedPreferencesPigeonOptions?); + assert(arg_options != null, + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getValue was null, expected non-null SharedPreferencesPigeonOptions.'); + try { + final Object? output = api.getValue(arg_key!, arg_options!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getKeys', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getKeys was null.'); + final List args = (message as List?)!; + final List? arg_allowList = + (args[0] as List?)?.cast(); + final SharedPreferencesPigeonOptions? arg_options = + (args[1] as SharedPreferencesPigeonOptions?); + assert(arg_options != null, + 'Argument for dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getKeys was null, expected non-null SharedPreferencesPigeonOptions.'); + try { + final List output = + api.getKeys(arg_allowList, arg_options!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } diff --git a/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md index cabbb80908e..0c831984b90 100644 --- a/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 2.4.0 +* Adds `SharedPreferencesAsyncLinux` API. * Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 2.3.2 diff --git a/packages/shared_preferences/shared_preferences_linux/example/integration_test/shared_preferences_test.dart b/packages/shared_preferences/shared_preferences_linux/example/integration_test/shared_preferences_test.dart index 5f06c87c317..370839f0e9c 100644 --- a/packages/shared_preferences/shared_preferences_linux/example/integration_test/shared_preferences_test.dart +++ b/packages/shared_preferences/shared_preferences_linux/example/integration_test/shared_preferences_test.dart @@ -5,6 +5,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:shared_preferences_linux/shared_preferences_linux.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; import 'package:shared_preferences_platform_interface/types.dart'; void main() { @@ -336,4 +337,199 @@ void main() { }); }); }); + + group('shared_preferences_async', () { + const SharedPreferencesLinuxOptions emptyOptions = + SharedPreferencesLinuxOptions(); + + const String stringKey = 'testString'; + const String boolKey = 'testBool'; + const String intKey = 'testInt'; + const String doubleKey = 'testDouble'; + const String listKey = 'testList'; + + const String testString = 'hello world'; + const bool testBool = true; + const int testInt = 42; + const double testDouble = 3.14159; + const List testList = ['foo', 'bar']; + + Future getPreferences() async { + final SharedPreferencesAsyncPlatform preferences = + SharedPreferencesAsyncPlatform.instance!; + await preferences.clear( + const ClearPreferencesParameters(filter: PreferencesFilters()), + emptyOptions); + return preferences; + } + + testWidgets('set and get String', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setString(stringKey, testString, emptyOptions); + expect(await preferences.getString(stringKey, emptyOptions), testString); + }); + + testWidgets('set and get bool', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setBool(boolKey, testBool, emptyOptions); + expect(await preferences.getBool(boolKey, emptyOptions), testBool); + }); + + testWidgets('set and get int', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setInt(intKey, testInt, emptyOptions); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + }); + + testWidgets('set and get double', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + }); + + testWidgets('set and get StringList', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setStringList(listKey, testList, emptyOptions); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); + + testWidgets('getPreferences', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, + ); + + expect(gotAll.length, 5); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + expect(gotAll[intKey], testInt); + expect(gotAll[doubleKey], testDouble); + expect(gotAll[listKey], testList); + }); + + testWidgets('getPreferences with filter', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + + expect(gotAll.length, 2); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + }); + + testWidgets('getKeys', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, + ); + + expect(keys.length, 5); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + expect(keys, contains(intKey)); + expect(keys, contains(doubleKey)); + expect(keys, contains(listKey)); + }); + + testWidgets('getKeys with filter', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + + expect(keys.length, 2); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + }); + + testWidgets('clear', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + await preferences.clear( + const ClearPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, + ); + + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), null); + expect(await preferences.getDouble(doubleKey, emptyOptions), null); + expect(await preferences.getStringList(listKey, emptyOptions), null); + }); + + testWidgets('clear with filter', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + await preferences.clear( + const ClearPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); + }); } diff --git a/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart b/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart index 050d2e50f64..b333716cb15 100644 --- a/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart +++ b/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart @@ -4,10 +4,9 @@ // ignore_for_file: public_member_api_docs -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:shared_preferences_linux/shared_preferences_linux.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; void main() { runApp(const MyApp()); @@ -33,26 +32,36 @@ class SharedPreferencesDemo extends StatefulWidget { } class SharedPreferencesDemoState extends State { - final SharedPreferencesLinux prefs = SharedPreferencesLinux(); + final SharedPreferencesAsyncPlatform? _prefs = + SharedPreferencesAsyncPlatform.instance; + final SharedPreferencesLinuxOptions options = + const SharedPreferencesLinuxOptions(); + static const String _counterKey = 'counter'; late Future _counter; Future _incrementCounter() async { - final Map values = await prefs.getAll(); - final int counter = (values['counter'] as int? ?? 0) + 1; + final int? value = await _prefs!.getInt(_counterKey, options); + final int counter = (value ?? 0) + 1; setState(() { - _counter = prefs.setValue('Int', 'counter', counter).then((bool success) { + _counter = _prefs.setInt(_counterKey, counter, options).then((_) { return counter; }); }); } + Future _getAndSetCounter() async { + setState(() { + _counter = _prefs!.getInt(_counterKey, options).then((int? counter) { + return counter ?? 0; + }); + }); + } + @override void initState() { super.initState(); - _counter = prefs.getAll().then((Map values) { - return values['counter'] as int? ?? 0; - }); + _getAndSetCounter(); } @override diff --git a/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml index 060ea7da5f4..f3704c92ac5 100644 --- a/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - shared_preferences_platform_interface: ^2.3.0 + shared_preferences_platform_interface: ^2.4.0 dev_dependencies: flutter_test: diff --git a/packages/shared_preferences/shared_preferences_linux/lib/shared_preferences_linux.dart b/packages/shared_preferences/shared_preferences_linux/lib/shared_preferences_linux.dart index e9f93489b6c..4c7e5268ce1 100644 --- a/packages/shared_preferences/shared_preferences_linux/lib/shared_preferences_linux.dart +++ b/packages/shared_preferences/shared_preferences_linux/lib/shared_preferences_linux.dart @@ -10,9 +10,14 @@ import 'package:file/local.dart'; import 'package:flutter/foundation.dart' show debugPrint, visibleForTesting; import 'package:path/path.dart' as path; import 'package:path_provider_linux/path_provider_linux.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; import 'package:shared_preferences_platform_interface/types.dart'; +const String _defaultFileName = 'shared_preferences'; + +const String _defaultPrefix = 'flutter.'; + /// The Linux implementation of [SharedPreferencesStorePlatform]. /// /// This class implements the `package:shared_preferences` functionality for Linux. @@ -22,11 +27,11 @@ class SharedPreferencesLinux extends SharedPreferencesStorePlatform { @Deprecated('Use `SharedPreferencesStorePlatform.instance` instead.') static SharedPreferencesLinux instance = SharedPreferencesLinux(); - static const String _defaultPrefix = 'flutter.'; - /// Registers the Linux implementation. static void registerWith() { SharedPreferencesStorePlatform.instance = SharedPreferencesLinux(); + // A temporary work-around for having two plugins contained in a single package. + SharedPreferencesAsyncLinux.registerWith(); } /// Local copy of preferences @@ -40,57 +45,15 @@ class SharedPreferencesLinux extends SharedPreferencesStorePlatform { @visibleForTesting PathProviderLinux pathProvider = PathProviderLinux(); - /// Gets the file where the preferences are stored. - Future _getLocalDataFile() async { - final String? directory = await pathProvider.getApplicationSupportPath(); - if (directory == null) { - return null; - } - return fs.file(path.join(directory, 'shared_preferences.json')); - } - - /// Gets the preferences from the stored file and saves them in cache. - Future> _reload() async { - Map preferences = {}; - final File? localDataFile = await _getLocalDataFile(); - if (localDataFile != null && localDataFile.existsSync()) { - final String stringMap = localDataFile.readAsStringSync(); - if (stringMap.isNotEmpty) { - final Object? data = json.decode(stringMap); - if (data is Map) { - preferences = data.cast(); - } - } - } - _cachedPreferences = preferences; - return preferences; - } - /// Checks for cached preferences and returns them or loads preferences from /// file and returns and caches them. Future> _readPreferences() async { - return _cachedPreferences ?? await _reload(); - } - - /// Writes the cached preferences to disk. Returns [true] if the operation - /// succeeded. - Future _writePreferences(Map preferences) async { - try { - final File? localDataFile = await _getLocalDataFile(); - if (localDataFile == null) { - debugPrint('Unable to determine where to write preferences.'); - return false; - } - if (!localDataFile.existsSync()) { - localDataFile.createSync(recursive: true); - } - final String stringMap = json.encode(preferences); - localDataFile.writeAsStringSync(stringMap); - } catch (e) { - debugPrint('Error saving preferences to disk: $e'); - return false; - } - return true; + _cachedPreferences ??= await _reload( + _defaultFileName, + fs: fs, + pathProvider: pathProvider, + ); + return _cachedPreferences!; } @override @@ -111,11 +74,17 @@ class SharedPreferencesLinux extends SharedPreferencesStorePlatform { @override Future clearWithParameters(ClearParameters parameters) async { final PreferencesFilter filter = parameters.filter; + final Map preferences = await _readPreferences(); preferences.removeWhere((String key, _) => key.startsWith(filter.prefix) && (filter.allowList == null || filter.allowList!.contains(key))); - return _writePreferences(preferences); + return _writePreferences( + preferences, + _defaultFileName, + fs: fs, + pathProvider: pathProvider, + ); } @override @@ -148,13 +117,296 @@ class SharedPreferencesLinux extends SharedPreferencesStorePlatform { Future remove(String key) async { final Map preferences = await _readPreferences(); preferences.remove(key); - return _writePreferences(preferences); + return _writePreferences( + preferences, + _defaultFileName, + fs: fs, + pathProvider: pathProvider, + ); } @override Future setValue(String valueType, String key, Object value) async { final Map preferences = await _readPreferences(); preferences[key] = value; - return _writePreferences(preferences); + return _writePreferences( + preferences, + _defaultFileName, + fs: fs, + pathProvider: pathProvider, + ); + } +} + +/// The Linux implementation of [SharedPreferencesAsyncPlatform]. +/// +/// This class implements the `package:shared_preferences` functionality for Linux. +base class SharedPreferencesAsyncLinux extends SharedPreferencesAsyncPlatform { + /// Registers the Linux implementation. + static void registerWith() { + SharedPreferencesAsyncPlatform.instance = SharedPreferencesAsyncLinux(); + } + + /// Local copy of preferences + Map? _cachedPreferences; + + /// File system used to store to disk. Exposed for testing only. + @visibleForTesting + FileSystem fs = const LocalFileSystem(); + + /// The path_provider_linux instance used to find the support directory. + @visibleForTesting + PathProviderLinux pathProvider = PathProviderLinux(); + + @override + Future> getKeys( + GetPreferencesParameters parameters, + SharedPreferencesOptions options, + ) async { + return (await getPreferences(parameters, options)).keys.toSet(); + } + + @override + Future setString( + String key, + String value, + SharedPreferencesOptions options, + ) { + return _setValue(key, value, options); + } + + @override + Future setBool( + String key, + bool value, + SharedPreferencesOptions options, + ) { + return _setValue(key, value, options); + } + + @override + Future setDouble( + String key, + double value, + SharedPreferencesOptions options, + ) { + return _setValue(key, value, options); + } + + @override + Future setInt( + String key, + int value, + SharedPreferencesOptions options, + ) { + return _setValue(key, value, options); + } + + @override + Future setStringList( + String key, + List value, + SharedPreferencesOptions options, + ) { + return _setValue(key, value, options); + } + + @override + Future getString( + String key, + SharedPreferencesOptions options, + ) async { + final Map data = await _readAll({key}, options); + return data[key] as String?; + } + + @override + Future getBool( + String key, + SharedPreferencesOptions options, + ) async { + final Map data = await _readAll({key}, options); + return data[key] as bool?; + } + + @override + Future getDouble( + String key, + SharedPreferencesOptions options, + ) async { + final Map data = await _readAll({key}, options); + return data[key] as double?; + } + + @override + Future getInt( + String key, + SharedPreferencesOptions options, + ) async { + final Map data = await _readAll({key}, options); + return data[key] as int?; + } + + @override + Future?> getStringList( + String key, + SharedPreferencesOptions options, + ) async { + final Map data = await _readAll({key}, options); + return (data[key] as List?)?.toList(); + } + + @override + Future clear(ClearPreferencesParameters parameters, + SharedPreferencesOptions options) async { + final SharedPreferencesLinuxOptions linuxOptions = + SharedPreferencesLinuxOptions.fromSharedPreferencesOptions(options); + final PreferencesFilters filter = parameters.filter; + final Map preferences = + await _readPreferences(linuxOptions.fileName); + preferences.removeWhere((String key, _) => + filter.allowList == null || filter.allowList!.contains(key)); + await _writePreferences( + preferences, + linuxOptions.fileName, + fs: fs, + pathProvider: pathProvider, + ); + } + + @override + Future> getPreferences( + GetPreferencesParameters parameters, + SharedPreferencesOptions options, + ) async { + return _readAll(parameters.filter.allowList, options); + } + + Future> _readAll( + Set? allowList, + SharedPreferencesOptions options, + ) async { + final SharedPreferencesLinuxOptions linuxOptions = + SharedPreferencesLinuxOptions.fromSharedPreferencesOptions(options); + final Map prefs = + Map.from(await _readPreferences(linuxOptions.fileName)); + prefs.removeWhere((String key, _) => !(allowList?.contains(key) ?? true)); + return prefs; + } + + Future _setValue( + String key, Object value, SharedPreferencesOptions options) async { + final SharedPreferencesLinuxOptions linuxOptions = + SharedPreferencesLinuxOptions.fromSharedPreferencesOptions(options); + final Map preferences = + await _readPreferences(linuxOptions.fileName); + preferences[key] = value; + await _writePreferences( + preferences, + linuxOptions.fileName, + fs: fs, + pathProvider: pathProvider, + ); + } + + /// Checks for cached preferences and returns them or loads preferences from + /// file and returns and caches them. + Future> _readPreferences(String fileName) async { + _cachedPreferences ??= await _reload( + fileName, + fs: fs, + pathProvider: pathProvider, + ); + return _cachedPreferences!; + } +} + +/// Gets the file where the preferences are stored. +Future _getLocalDataFile( + String fileName, { + FileSystem fs = const LocalFileSystem(), + PathProviderLinux? pathProvider, +}) async { + pathProvider = pathProvider ?? PathProviderLinux(); + final String? directory = await pathProvider.getApplicationSupportPath(); + if (directory == null) { + return null; + } + final String fileLocation = path.join(directory, '$fileName.json'); + return fs.file(fileLocation); +} + +/// Gets the preferences from the stored file and saves them in cache. +Future> _reload( + String fileName, { + FileSystem fs = const LocalFileSystem(), + PathProviderLinux? pathProvider, +}) async { + Map preferences = {}; + final File? localDataFile = await _getLocalDataFile( + fileName, + fs: fs, + pathProvider: pathProvider, + ); + if (localDataFile != null && localDataFile.existsSync()) { + final String stringMap = localDataFile.readAsStringSync(); + if (stringMap.isNotEmpty) { + final Object? data = json.decode(stringMap); + if (data is Map) { + preferences = data.cast(); + } + } + } + return preferences; +} + +/// Writes the cached preferences to disk. Returns [true] if the operation +/// succeeded. +Future _writePreferences( + Map preferences, + String fileName, { + FileSystem fs = const LocalFileSystem(), + PathProviderLinux? pathProvider, +}) async { + try { + final File? localDataFile = await _getLocalDataFile( + fileName, + fs: fs, + pathProvider: pathProvider, + ); + if (localDataFile == null) { + debugPrint('Unable to determine where to write preferences.'); + return false; + } + if (!localDataFile.existsSync()) { + localDataFile.createSync(recursive: true); + } + final String stringMap = json.encode(preferences); + localDataFile.writeAsStringSync(stringMap); + } catch (e) { + debugPrint('Error saving preferences to disk: $e'); + return false; + } + return true; +} + +/// Linux specific SharedPreferences Options. +class SharedPreferencesLinuxOptions extends SharedPreferencesOptions { + /// Constructor for SharedPreferencesLinuxOptions. + const SharedPreferencesLinuxOptions({ + this.fileName = 'shared_preferences', + }); + + /// The name of the file to store preferences in. + final String fileName; + + /// Returns a new instance of [SharedPreferencesLinuxOptions] from an existing + /// [SharedPreferencesOptions]. + static SharedPreferencesLinuxOptions fromSharedPreferencesOptions( + SharedPreferencesOptions options) { + if (options is SharedPreferencesLinuxOptions) { + return options; + } + return const SharedPreferencesLinuxOptions(); } } diff --git a/packages/shared_preferences/shared_preferences_linux/pubspec.yaml b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml index fab1ffeafd5..91a49c59840 100644 --- a/packages/shared_preferences/shared_preferences_linux/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml @@ -2,7 +2,7 @@ name: shared_preferences_linux description: Linux implementation of the shared_preferences plugin repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_linux issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.3.2 +version: 2.4.0 environment: sdk: ^3.2.0 @@ -22,7 +22,7 @@ dependencies: path: ^1.8.0 path_provider_linux: ^2.0.0 path_provider_platform_interface: ^2.0.0 - shared_preferences_platform_interface: ^2.3.0 + shared_preferences_platform_interface: ^2.4.0 dev_dependencies: flutter_test: diff --git a/packages/shared_preferences/shared_preferences_linux/test/fake_path_provider_linux.dart b/packages/shared_preferences/shared_preferences_linux/test/fake_path_provider_linux.dart new file mode 100644 index 00000000000..f726abe3856 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/test/fake_path_provider_linux.dart @@ -0,0 +1,29 @@ +// 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:flutter_test/flutter_test.dart'; +import 'package:path_provider_linux/path_provider_linux.dart'; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; + +/// Fake implementation of PathProviderLinux that returns hard-coded paths, +/// allowing tests to run on any platform. +/// +/// Note that this should only be used with an in-memory filesystem, as the +/// path it returns is a root path that does not actually exist on Linux. +class FakePathProviderLinux extends PathProviderPlatform + implements PathProviderLinux { + @override + Future getApplicationSupportPath() async => r'/appsupport'; + + @override + Future getTemporaryPath() async => null; + + @override + Future getLibraryPath() async => null; + + @override + Future getApplicationDocumentsPath() async => null; + + @override + Future getDownloadsPath() async => null; +} diff --git a/packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_test.dart b/packages/shared_preferences/shared_preferences_linux/test/legacy_shared_preferences_linux_test.dart similarity index 90% rename from packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_test.dart rename to packages/shared_preferences/shared_preferences_linux/test/legacy_shared_preferences_linux_test.dart index 185feb58c41..06faaa5c1e8 100644 --- a/packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_test.dart +++ b/packages/shared_preferences/shared_preferences_linux/test/legacy_shared_preferences_linux_test.dart @@ -7,11 +7,12 @@ import 'package:file/memory.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart' as path; import 'package:path_provider_linux/path_provider_linux.dart'; -import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; import 'package:shared_preferences_linux/shared_preferences_linux.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; import 'package:shared_preferences_platform_interface/types.dart'; +import 'fake_path_provider_linux.dart'; + void main() { late MemoryFileSystem fs; late PathProviderLinux pathProvider; @@ -75,7 +76,7 @@ void main() { return prefs; } - test('registered instance', () { + test('registered instance', () async { SharedPreferencesLinux.registerWith(); expect( SharedPreferencesStorePlatform.instance, isA()); @@ -254,26 +255,3 @@ void main() { expect(noValues, hasLength(0)); }); } - -/// Fake implementation of PathProviderLinux that returns hard-coded paths, -/// allowing tests to run on any platform. -/// -/// Note that this should only be used with an in-memory filesystem, as the -/// path it returns is a root path that does not actually exist on Linux. -class FakePathProviderLinux extends PathProviderPlatform - implements PathProviderLinux { - @override - Future getApplicationSupportPath() async => r'/appsupport'; - - @override - Future getTemporaryPath() async => null; - - @override - Future getLibraryPath() async => null; - - @override - Future getApplicationDocumentsPath() async => null; - - @override - Future getDownloadsPath() async => null; -} diff --git a/packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_async_test.dart b/packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_async_test.dart new file mode 100755 index 00000000000..d49bdc48eea --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_async_test.dart @@ -0,0 +1,203 @@ +// 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:file/memory.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:path_provider_linux/path_provider_linux.dart'; +import 'package:shared_preferences_linux/shared_preferences_linux.dart'; +import 'package:shared_preferences_platform_interface/types.dart'; + +import 'fake_path_provider_linux.dart'; + +void main() { + late MemoryFileSystem fs; + late PathProviderLinux pathProvider; + + SharedPreferencesAsyncLinux.registerWith(); + + const String stringKey = 'testString'; + const String boolKey = 'testBool'; + const String intKey = 'testInt'; + const String doubleKey = 'testDouble'; + const String listKey = 'testList'; + + const String testString = 'hello world'; + const bool testBool = true; + const int testInt = 42; + const double testDouble = 3.14159; + const List testList = ['foo', 'bar']; + + const SharedPreferencesLinuxOptions emptyOptions = + SharedPreferencesLinuxOptions(); + + setUp(() { + fs = MemoryFileSystem.test(); + pathProvider = FakePathProviderLinux(); + }); + + SharedPreferencesAsyncLinux getPreferences() { + final SharedPreferencesAsyncLinux prefs = SharedPreferencesAsyncLinux(); + prefs.fs = fs; + prefs.pathProvider = pathProvider; + return prefs; + } + + test('set and get String', () async { + final SharedPreferencesAsyncLinux preferences = getPreferences(); + + await preferences.setString(stringKey, testString, emptyOptions); + expect(await preferences.getString(stringKey, emptyOptions), testString); + }); + + test('set and get bool', () async { + final SharedPreferencesAsyncLinux preferences = getPreferences(); + + await preferences.setBool(boolKey, testBool, emptyOptions); + expect(await preferences.getBool(boolKey, emptyOptions), testBool); + }); + + test('set and get int', () async { + final SharedPreferencesAsyncLinux preferences = getPreferences(); + + await preferences.setInt(intKey, testInt, emptyOptions); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + }); + + test('set and get double', () async { + final SharedPreferencesAsyncLinux preferences = getPreferences(); + + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + }); + + test('set and get StringList', () async { + final SharedPreferencesAsyncLinux preferences = getPreferences(); + + await preferences.setStringList(listKey, testList, emptyOptions); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); + + test('getPreferences', () async { + final SharedPreferencesAsyncLinux preferences = getPreferences(); + + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions); + + expect(gotAll.length, 5); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + expect(gotAll[intKey], testInt); + expect(gotAll[doubleKey], testDouble); + expect(gotAll[listKey], testList); + }); + + test('getPreferences with filter', () async { + final SharedPreferencesAsyncLinux preferences = getPreferences(); + + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters( + filter: + PreferencesFilters(allowList: {stringKey, boolKey})), + emptyOptions); + + expect(gotAll.length, 2); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + }); + + test('getKeys', () async { + final SharedPreferencesAsyncLinux preferences = getPreferences(); + + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, + ); + + expect(keys.length, 5); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + expect(keys, contains(intKey)); + expect(keys, contains(doubleKey)); + expect(keys, contains(listKey)); + }); + + test('getKeys with filter', () async { + final SharedPreferencesAsyncLinux preferences = getPreferences(); + + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + + expect(keys.length, 2); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + }); + + test('clear', () async { + final SharedPreferencesAsyncLinux preferences = getPreferences(); + + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + await preferences.clear( + const ClearPreferencesParameters(filter: PreferencesFilters()), + emptyOptions); + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), null); + expect(await preferences.getDouble(doubleKey, emptyOptions), null); + expect(await preferences.getStringList(listKey, emptyOptions), null); + }); + + test('clear with filter', () async { + final SharedPreferencesAsyncLinux preferences = getPreferences(); + + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + await preferences.clear( + const ClearPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); +} diff --git a/packages/shared_preferences/shared_preferences_web/CHANGELOG.md b/packages/shared_preferences/shared_preferences_web/CHANGELOG.md index 5cb8dc3b612..dd8bf77ad13 100644 --- a/packages/shared_preferences/shared_preferences_web/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.4.0 + +* Adds `SharedPreferencesAsyncWeb` API. + ## 2.3.0 * Updates web code to package `web: ^0.5.0`. diff --git a/packages/shared_preferences/shared_preferences_web/example/integration_test/shared_preferences_web_test.dart b/packages/shared_preferences/shared_preferences_web/example/integration_test/shared_preferences_web_test.dart index 0238ba578a4..aeb1a6c2a65 100644 --- a/packages/shared_preferences/shared_preferences_web/example/integration_test/shared_preferences_web_test.dart +++ b/packages/shared_preferences/shared_preferences_web/example/integration_test/shared_preferences_web_test.dart @@ -5,6 +5,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:shared_preferences_platform_interface/method_channel_shared_preferences.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; import 'package:shared_preferences_platform_interface/types.dart'; import 'package:shared_preferences_web/shared_preferences_web.dart'; @@ -17,357 +18,564 @@ void main() { setUp(() { html.window.localStorage.clear(); }); + group('shared_preferences_web', () { + testWidgets('registers itself', (WidgetTester tester) async { + SharedPreferencesStorePlatform.instance = + MethodChannelSharedPreferencesStore(); + expect(SharedPreferencesStorePlatform.instance, + isNot(isA())); + SharedPreferencesPlugin.registerWith(null); + expect(SharedPreferencesStorePlatform.instance, + isA()); + }); - testWidgets('registers itself', (WidgetTester tester) async { - SharedPreferencesStorePlatform.instance = - MethodChannelSharedPreferencesStore(); - expect(SharedPreferencesStorePlatform.instance, - isNot(isA())); - SharedPreferencesPlugin.registerWith(null); - expect(SharedPreferencesStorePlatform.instance, - isA()); - }); - - const Map flutterTestValues = { - 'flutter.String': 'hello world', - 'flutter.Bool': true, - 'flutter.Int': 42, - 'flutter.Double': 3.14159, - 'flutter.StringList': ['foo', 'bar'], - }; - - const Map prefixTestValues = { - 'prefix.String': 'hello world', - 'prefix.Bool': true, - 'prefix.Int': 42, - 'prefix.Double': 3.14159, - 'prefix.StringList': ['foo', 'bar'], - }; - - const Map nonPrefixTestValues = { - 'String': 'hello world', - 'Bool': true, - 'Int': 42, - 'Double': 3.14159, - 'StringList': ['foo', 'bar'], - }; - - final Map allTestValues = {}; - - allTestValues.addAll(flutterTestValues); - allTestValues.addAll(prefixTestValues); - allTestValues.addAll(nonPrefixTestValues); - - late SharedPreferencesStorePlatform preferences; - - setUp(() async { - preferences = SharedPreferencesStorePlatform.instance; - }); - - tearDown(() async { - await preferences.clearWithParameters( - ClearParameters( - filter: PreferencesFilter(prefix: ''), - ), - ); - }); - - testWidgets('reading', (WidgetTester _) async { - final Map values = await preferences.getAll(); - expect(values.length, 0); - }); - - Future addData() async { - await preferences.setValue('String', 'String', allTestValues['String']!); - await preferences.setValue('Bool', 'Bool', allTestValues['Bool']!); - await preferences.setValue('Int', 'Int', allTestValues['Int']!); - await preferences.setValue('Double', 'Double', allTestValues['Double']!); - await preferences.setValue( - 'StringList', 'StringList', allTestValues['StringList']!); - await preferences.setValue( - 'String', 'prefix.String', allTestValues['prefix.String']!); - await preferences.setValue( - 'Bool', 'prefix.Bool', allTestValues['prefix.Bool']!); - await preferences.setValue( - 'Int', 'prefix.Int', allTestValues['prefix.Int']!); - await preferences.setValue( - 'Double', 'prefix.Double', allTestValues['prefix.Double']!); - await preferences.setValue( - 'StringList', 'prefix.StringList', allTestValues['prefix.StringList']!); - await preferences.setValue( - 'String', 'flutter.String', allTestValues['flutter.String']!); - await preferences.setValue( - 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!); - await preferences.setValue( - 'Int', 'flutter.Int', allTestValues['flutter.Int']!); - await preferences.setValue( - 'Double', 'flutter.Double', allTestValues['flutter.Double']!); - await preferences.setValue('StringList', 'flutter.StringList', - allTestValues['flutter.StringList']!); - } - - testWidgets('keys', (WidgetTester _) async { - await addData(); - final Iterable keys = html.window.localStorage.keys; - final Iterable expectedKeys = allTestValues.keys; - - expect(keys, hasLength(expectedKeys.length)); - expect(keys, containsAll(expectedKeys)); - }); + const Map flutterTestValues = { + 'flutter.String': 'hello world', + 'flutter.Bool': true, + 'flutter.Int': 42, + 'flutter.Double': 3.14159, + 'flutter.StringList': ['foo', 'bar'], + }; + + const Map prefixTestValues = { + 'prefix.String': 'hello world', + 'prefix.Bool': true, + 'prefix.Int': 42, + 'prefix.Double': 3.14159, + 'prefix.StringList': ['foo', 'bar'], + }; + + const Map nonPrefixTestValues = { + 'String': 'hello world', + 'Bool': true, + 'Int': 42, + 'Double': 3.14159, + 'StringList': ['foo', 'bar'], + }; + + final Map allTestValues = {}; + + allTestValues.addAll(flutterTestValues); + allTestValues.addAll(prefixTestValues); + allTestValues.addAll(nonPrefixTestValues); + + late SharedPreferencesStorePlatform preferences; - testWidgets('clear', (WidgetTester _) async { - await addData(); - await preferences.clear(); - final Map values = await preferences.getAll(); - expect(values['flutter.String'], null); - expect(values['flutter.Bool'], null); - expect(values['flutter.Int'], null); - expect(values['flutter.Double'], null); - expect(values['flutter.StringList'], null); - }); - - group('withPrefix', () { setUp(() async { - await addData(); + preferences = SharedPreferencesStorePlatform.instance; }); - testWidgets('remove', (WidgetTester _) async { - const String key = 'flutter.String'; - await preferences.remove(key); - final Map values = - // ignore: deprecated_member_use - await preferences.getAllWithPrefix(''); - expect(values[key], isNull); + tearDown(() async { + await preferences.clearWithParameters( + ClearParameters( + filter: PreferencesFilter(prefix: ''), + ), + ); }); - testWidgets('get all with prefix', (WidgetTester _) async { - final Map values = - // ignore: deprecated_member_use - await preferences.getAllWithPrefix('prefix.'); - expect(values['prefix.String'], allTestValues['prefix.String']); - expect(values['prefix.Bool'], allTestValues['prefix.Bool']); - expect(values['prefix.Int'], allTestValues['prefix.Int']); - expect(values['prefix.Double'], allTestValues['prefix.Double']); - expect(values['prefix.StringList'], allTestValues['prefix.StringList']); + testWidgets('reading', (WidgetTester _) async { + final Map values = await preferences.getAll(); + expect(values.length, 0); }); - testWidgets('getAllWithNoPrefix', (WidgetTester _) async { - final Map values = - // ignore: deprecated_member_use - await preferences.getAllWithPrefix(''); - expect(values['String'], allTestValues['String']); - expect(values['Bool'], allTestValues['Bool']); - expect(values['Int'], allTestValues['Int']); - expect(values['Double'], allTestValues['Double']); - expect(values['StringList'], allTestValues['StringList']); - expect(values['flutter.String'], allTestValues['flutter.String']); - expect(values['flutter.Bool'], allTestValues['flutter.Bool']); - expect(values['flutter.Int'], allTestValues['flutter.Int']); - expect(values['flutter.Double'], allTestValues['flutter.Double']); - expect(values['flutter.StringList'], allTestValues['flutter.StringList']); - }); + Future addData() async { + await preferences.setValue('String', 'String', allTestValues['String']!); + await preferences.setValue('Bool', 'Bool', allTestValues['Bool']!); + await preferences.setValue('Int', 'Int', allTestValues['Int']!); + await preferences.setValue('Double', 'Double', allTestValues['Double']!); + await preferences.setValue( + 'StringList', 'StringList', allTestValues['StringList']!); + await preferences.setValue( + 'String', 'prefix.String', allTestValues['prefix.String']!); + await preferences.setValue( + 'Bool', 'prefix.Bool', allTestValues['prefix.Bool']!); + await preferences.setValue( + 'Int', 'prefix.Int', allTestValues['prefix.Int']!); + await preferences.setValue( + 'Double', 'prefix.Double', allTestValues['prefix.Double']!); + await preferences.setValue('StringList', 'prefix.StringList', + allTestValues['prefix.StringList']!); + await preferences.setValue( + 'String', 'flutter.String', allTestValues['flutter.String']!); + await preferences.setValue( + 'Bool', 'flutter.Bool', allTestValues['flutter.Bool']!); + await preferences.setValue( + 'Int', 'flutter.Int', allTestValues['flutter.Int']!); + await preferences.setValue( + 'Double', 'flutter.Double', allTestValues['flutter.Double']!); + await preferences.setValue('StringList', 'flutter.StringList', + allTestValues['flutter.StringList']!); + } - testWidgets('clearWithPrefix', (WidgetTester _) async { - // ignore: deprecated_member_use - await preferences.clearWithPrefix('prefix.'); - Map values = - // ignore: deprecated_member_use - await preferences.getAllWithPrefix('prefix.'); - expect(values['prefix.String'], null); - expect(values['prefix.Bool'], null); - expect(values['prefix.Int'], null); - expect(values['prefix.Double'], null); - expect(values['prefix.StringList'], null); - // ignore: deprecated_member_use - values = await preferences.getAllWithPrefix('flutter.'); - expect(values['flutter.String'], allTestValues['flutter.String']); - expect(values['flutter.Bool'], allTestValues['flutter.Bool']); - expect(values['flutter.Int'], allTestValues['flutter.Int']); - expect(values['flutter.Double'], allTestValues['flutter.Double']); - expect(values['flutter.StringList'], allTestValues['flutter.StringList']); + testWidgets('keys', (WidgetTester _) async { + await addData(); + final Iterable keys = html.window.localStorage.keys; + final Iterable expectedKeys = allTestValues.keys; + + expect(keys, hasLength(expectedKeys.length)); + expect(keys, containsAll(expectedKeys)); }); - testWidgets('clearWithNoPrefix', (WidgetTester _) async { - // ignore: deprecated_member_use - await preferences.clearWithPrefix(''); - final Map values = - // ignore: deprecated_member_use - await preferences.getAllWithPrefix(''); - expect(values['String'], null); - expect(values['Bool'], null); - expect(values['Int'], null); - expect(values['Double'], null); - expect(values['StringList'], null); + testWidgets('clear', (WidgetTester _) async { + await addData(); + await preferences.clear(); + final Map values = await preferences.getAll(); expect(values['flutter.String'], null); expect(values['flutter.Bool'], null); expect(values['flutter.Int'], null); expect(values['flutter.Double'], null); expect(values['flutter.StringList'], null); }); - }); - group('withParameters', () { - setUp(() async { - await addData(); + group('withPrefix', () { + setUp(() async { + await addData(); + }); + + testWidgets('remove', (WidgetTester _) async { + const String key = 'flutter.String'; + await preferences.remove(key); + final Map values = + // ignore: deprecated_member_use + await preferences.getAllWithPrefix(''); + expect(values[key], isNull); + }); + + testWidgets('get all with prefix', (WidgetTester _) async { + final Map values = + // ignore: deprecated_member_use + await preferences.getAllWithPrefix('prefix.'); + expect(values['prefix.String'], allTestValues['prefix.String']); + expect(values['prefix.Bool'], allTestValues['prefix.Bool']); + expect(values['prefix.Int'], allTestValues['prefix.Int']); + expect(values['prefix.Double'], allTestValues['prefix.Double']); + expect(values['prefix.StringList'], allTestValues['prefix.StringList']); + }); + + testWidgets('getAllWithNoPrefix', (WidgetTester _) async { + final Map values = + // ignore: deprecated_member_use + await preferences.getAllWithPrefix(''); + expect(values['String'], allTestValues['String']); + expect(values['Bool'], allTestValues['Bool']); + expect(values['Int'], allTestValues['Int']); + expect(values['Double'], allTestValues['Double']); + expect(values['StringList'], allTestValues['StringList']); + expect(values['flutter.String'], allTestValues['flutter.String']); + expect(values['flutter.Bool'], allTestValues['flutter.Bool']); + expect(values['flutter.Int'], allTestValues['flutter.Int']); + expect(values['flutter.Double'], allTestValues['flutter.Double']); + expect( + values['flutter.StringList'], allTestValues['flutter.StringList']); + }); + + testWidgets('clearWithPrefix', (WidgetTester _) async { + // ignore: deprecated_member_use + await preferences.clearWithPrefix('prefix.'); + Map values = + // ignore: deprecated_member_use + await preferences.getAllWithPrefix('prefix.'); + expect(values['prefix.String'], null); + expect(values['prefix.Bool'], null); + expect(values['prefix.Int'], null); + expect(values['prefix.Double'], null); + expect(values['prefix.StringList'], null); + // ignore: deprecated_member_use + values = await preferences.getAllWithPrefix('flutter.'); + expect(values['flutter.String'], allTestValues['flutter.String']); + expect(values['flutter.Bool'], allTestValues['flutter.Bool']); + expect(values['flutter.Int'], allTestValues['flutter.Int']); + expect(values['flutter.Double'], allTestValues['flutter.Double']); + expect( + values['flutter.StringList'], allTestValues['flutter.StringList']); + }); + + testWidgets('clearWithNoPrefix', (WidgetTester _) async { + // ignore: deprecated_member_use + await preferences.clearWithPrefix(''); + final Map values = + // ignore: deprecated_member_use + await preferences.getAllWithPrefix(''); + expect(values['String'], null); + expect(values['Bool'], null); + expect(values['Int'], null); + expect(values['Double'], null); + expect(values['StringList'], null); + expect(values['flutter.String'], null); + expect(values['flutter.Bool'], null); + expect(values['flutter.Int'], null); + expect(values['flutter.Double'], null); + expect(values['flutter.StringList'], null); + }); + }); + + group('withParameters', () { + setUp(() async { + await addData(); + }); + + testWidgets('remove', (WidgetTester _) async { + const String key = 'flutter.String'; + await preferences.remove(key); + final Map values = + await preferences.getAllWithParameters( + GetAllParameters( + filter: PreferencesFilter(prefix: ''), + ), + ); + expect(values[key], isNull); + }); + + testWidgets('get all with prefix', (WidgetTester _) async { + final Map values = + await preferences.getAllWithParameters( + GetAllParameters( + filter: PreferencesFilter(prefix: 'prefix.'), + ), + ); + expect(values['prefix.String'], allTestValues['prefix.String']); + expect(values['prefix.Bool'], allTestValues['prefix.Bool']); + expect(values['prefix.Int'], allTestValues['prefix.Int']); + expect(values['prefix.Double'], allTestValues['prefix.Double']); + expect(values['prefix.StringList'], allTestValues['prefix.StringList']); + }); + + testWidgets('get all with allow list', (WidgetTester _) async { + final Map values = + await preferences.getAllWithParameters( + GetAllParameters( + filter: PreferencesFilter( + prefix: 'prefix.', + allowList: {'prefix.String'}, + ), + ), + ); + expect(values['prefix.String'], allTestValues['prefix.String']); + expect(values['prefix.Bool'], null); + expect(values['prefix.Int'], null); + expect(values['prefix.Double'], null); + expect(values['prefix.StringList'], null); + }); + + testWidgets('getAllWithNoPrefix', (WidgetTester _) async { + final Map values = + await preferences.getAllWithParameters( + GetAllParameters( + filter: PreferencesFilter(prefix: ''), + ), + ); + expect(values['String'], allTestValues['String']); + expect(values['Bool'], allTestValues['Bool']); + expect(values['Int'], allTestValues['Int']); + expect(values['Double'], allTestValues['Double']); + expect(values['StringList'], allTestValues['StringList']); + expect(values['flutter.String'], allTestValues['flutter.String']); + expect(values['flutter.Bool'], allTestValues['flutter.Bool']); + expect(values['flutter.Int'], allTestValues['flutter.Int']); + expect(values['flutter.Double'], allTestValues['flutter.Double']); + expect( + values['flutter.StringList'], allTestValues['flutter.StringList']); + }); + + testWidgets('clearWithParameters', (WidgetTester _) async { + await preferences.clearWithParameters( + ClearParameters( + filter: PreferencesFilter(prefix: 'prefix.'), + ), + ); + Map values = await preferences.getAllWithParameters( + GetAllParameters( + filter: PreferencesFilter(prefix: 'prefix.'), + ), + ); + expect(values['prefix.String'], null); + expect(values['prefix.Bool'], null); + expect(values['prefix.Int'], null); + expect(values['prefix.Double'], null); + expect(values['prefix.StringList'], null); + values = await preferences.getAllWithParameters( + GetAllParameters( + filter: PreferencesFilter(prefix: 'flutter.'), + ), + ); + expect(values['flutter.String'], allTestValues['flutter.String']); + expect(values['flutter.Bool'], allTestValues['flutter.Bool']); + expect(values['flutter.Int'], allTestValues['flutter.Int']); + expect(values['flutter.Double'], allTestValues['flutter.Double']); + expect( + values['flutter.StringList'], allTestValues['flutter.StringList']); + }); + + testWidgets('clearWithParameters with allow list', + (WidgetTester _) async { + await addData(); + await preferences.clearWithParameters( + ClearParameters( + filter: PreferencesFilter( + prefix: 'prefix.', + allowList: {'prefix.StringList'}, + ), + ), + ); + Map values = await preferences.getAllWithParameters( + GetAllParameters( + filter: PreferencesFilter(prefix: 'prefix.'), + ), + ); + expect(values['prefix.String'], allTestValues['prefix.String']); + expect(values['prefix.Bool'], allTestValues['prefix.Bool']); + expect(values['prefix.Int'], allTestValues['prefix.Int']); + expect(values['prefix.Double'], allTestValues['prefix.Double']); + expect(values['prefix.StringList'], null); + values = await preferences.getAllWithParameters( + GetAllParameters( + filter: PreferencesFilter(prefix: 'flutter.'), + ), + ); + expect(values['flutter.String'], allTestValues['flutter.String']); + expect(values['flutter.Bool'], allTestValues['flutter.Bool']); + expect(values['flutter.Int'], allTestValues['flutter.Int']); + expect(values['flutter.Double'], allTestValues['flutter.Double']); + expect( + values['flutter.StringList'], allTestValues['flutter.StringList']); + }); + + testWidgets('clearWithNoPrefix', (WidgetTester _) async { + await preferences.clearWithParameters( + ClearParameters( + filter: PreferencesFilter(prefix: ''), + ), + ); + final Map values = + await preferences.getAllWithParameters( + GetAllParameters( + filter: PreferencesFilter(prefix: ''), + ), + ); + expect(values['String'], null); + expect(values['Bool'], null); + expect(values['Int'], null); + expect(values['Double'], null); + expect(values['StringList'], null); + expect(values['flutter.String'], null); + expect(values['flutter.Bool'], null); + expect(values['flutter.Int'], null); + expect(values['flutter.Double'], null); + expect(values['flutter.StringList'], null); + }); }); - testWidgets('remove', (WidgetTester _) async { - const String key = 'flutter.String'; - await preferences.remove(key); + testWidgets('simultaneous writes', (WidgetTester _) async { + final List> writes = >[]; + const int writeCount = 100; + for (int i = 1; i <= writeCount; i++) { + writes.add(preferences.setValue('Int', 'Int', i)); + } + final List result = await Future.wait(writes, eagerError: true); + // All writes should succeed. + expect(result.where((bool element) => !element), isEmpty); + // The last write should win. final Map values = await preferences.getAllWithParameters( GetAllParameters( filter: PreferencesFilter(prefix: ''), ), ); - expect(values[key], isNull); + expect(values['Int'], writeCount); }); + }); - testWidgets('get all with prefix', (WidgetTester _) async { - final Map values = await preferences.getAllWithParameters( - GetAllParameters( - filter: PreferencesFilter(prefix: 'prefix.'), - ), - ); - expect(values['prefix.String'], allTestValues['prefix.String']); - expect(values['prefix.Bool'], allTestValues['prefix.Bool']); - expect(values['prefix.Int'], allTestValues['prefix.Int']); - expect(values['prefix.Double'], allTestValues['prefix.Double']); - expect(values['prefix.StringList'], allTestValues['prefix.StringList']); + group('shared_preferences_async', () { + const SharedPreferencesWebOptions emptyOptions = + SharedPreferencesWebOptions(); + + const String stringKey = 'testString'; + const String boolKey = 'testBool'; + const String intKey = 'testInt'; + const String doubleKey = 'testDouble'; + const String listKey = 'testList'; + + const String testString = 'hello world'; + const bool testBool = true; + const int testInt = 42; + const double testDouble = 3.14159; + const List testList = ['foo', 'bar']; + + Future getPreferences() async { + final SharedPreferencesAsyncPlatform preferences = + SharedPreferencesAsyncPlatform.instance!; + await preferences.clear( + const ClearPreferencesParameters(filter: PreferencesFilters()), + emptyOptions); + return preferences; + } + + testWidgets('set and get String', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setString(stringKey, testString, emptyOptions); + expect(await preferences.getString(stringKey, emptyOptions), testString); }); - testWidgets('get all with allow list', (WidgetTester _) async { - final Map values = await preferences.getAllWithParameters( - GetAllParameters( - filter: PreferencesFilter( - prefix: 'prefix.', - allowList: {'prefix.String'}, - ), - ), - ); - expect(values['prefix.String'], allTestValues['prefix.String']); - expect(values['prefix.Bool'], null); - expect(values['prefix.Int'], null); - expect(values['prefix.Double'], null); - expect(values['prefix.StringList'], null); + testWidgets('set and get bool', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setBool(boolKey, testBool, emptyOptions); + expect(await preferences.getBool(boolKey, emptyOptions), testBool); }); - testWidgets('getAllWithNoPrefix', (WidgetTester _) async { - final Map values = await preferences.getAllWithParameters( - GetAllParameters( - filter: PreferencesFilter(prefix: ''), - ), - ); - expect(values['String'], allTestValues['String']); - expect(values['Bool'], allTestValues['Bool']); - expect(values['Int'], allTestValues['Int']); - expect(values['Double'], allTestValues['Double']); - expect(values['StringList'], allTestValues['StringList']); - expect(values['flutter.String'], allTestValues['flutter.String']); - expect(values['flutter.Bool'], allTestValues['flutter.Bool']); - expect(values['flutter.Int'], allTestValues['flutter.Int']); - expect(values['flutter.Double'], allTestValues['flutter.Double']); - expect(values['flutter.StringList'], allTestValues['flutter.StringList']); + testWidgets('set and get int', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setInt(intKey, testInt, emptyOptions); + expect(await preferences.getInt(intKey, emptyOptions), testInt); }); - testWidgets('clearWithParameters', (WidgetTester _) async { - await preferences.clearWithParameters( - ClearParameters( - filter: PreferencesFilter(prefix: 'prefix.'), - ), - ); - Map values = await preferences.getAllWithParameters( - GetAllParameters( - filter: PreferencesFilter(prefix: 'prefix.'), - ), - ); - expect(values['prefix.String'], null); - expect(values['prefix.Bool'], null); - expect(values['prefix.Int'], null); - expect(values['prefix.Double'], null); - expect(values['prefix.StringList'], null); - values = await preferences.getAllWithParameters( - GetAllParameters( - filter: PreferencesFilter(prefix: 'flutter.'), - ), + testWidgets('set and get double', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + }); + + testWidgets('set and get StringList', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setStringList(listKey, testList, emptyOptions); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); + + testWidgets('getPreferences', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, ); - expect(values['flutter.String'], allTestValues['flutter.String']); - expect(values['flutter.Bool'], allTestValues['flutter.Bool']); - expect(values['flutter.Int'], allTestValues['flutter.Int']); - expect(values['flutter.Double'], allTestValues['flutter.Double']); - expect(values['flutter.StringList'], allTestValues['flutter.StringList']); + + expect(gotAll.length, 5); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + expect(gotAll[intKey], testInt); + expect(gotAll[doubleKey], testDouble); + expect(gotAll[listKey], testList); }); - testWidgets('clearWithParameters with allow list', (WidgetTester _) async { - await addData(); - await preferences.clearWithParameters( - ClearParameters( - filter: PreferencesFilter( - prefix: 'prefix.', - allowList: {'prefix.StringList'}, - ), + testWidgets('getPreferences with filter', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), ), + emptyOptions, ); - Map values = await preferences.getAllWithParameters( - GetAllParameters( - filter: PreferencesFilter(prefix: 'prefix.'), - ), + + expect(gotAll.length, 2); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + }); + + testWidgets('getKeys', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, ); - expect(values['prefix.String'], allTestValues['prefix.String']); - expect(values['prefix.Bool'], allTestValues['prefix.Bool']); - expect(values['prefix.Int'], allTestValues['prefix.Int']); - expect(values['prefix.Double'], allTestValues['prefix.Double']); - expect(values['prefix.StringList'], null); - values = await preferences.getAllWithParameters( - GetAllParameters( - filter: PreferencesFilter(prefix: 'flutter.'), + + expect(keys.length, 5); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + expect(keys, contains(intKey)); + expect(keys, contains(doubleKey)); + expect(keys, contains(listKey)); + }); + + testWidgets('getKeys with filter', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), ), + emptyOptions, ); - expect(values['flutter.String'], allTestValues['flutter.String']); - expect(values['flutter.Bool'], allTestValues['flutter.Bool']); - expect(values['flutter.Int'], allTestValues['flutter.Int']); - expect(values['flutter.Double'], allTestValues['flutter.Double']); - expect(values['flutter.StringList'], allTestValues['flutter.StringList']); + + expect(keys.length, 2); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); }); - testWidgets('clearWithNoPrefix', (WidgetTester _) async { - await preferences.clearWithParameters( - ClearParameters( - filter: PreferencesFilter(prefix: ''), - ), + testWidgets('clear', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + + await preferences.clear( + const ClearPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, ); - final Map values = await preferences.getAllWithParameters( - GetAllParameters( - filter: PreferencesFilter(prefix: ''), + + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), null); + expect(await preferences.getDouble(doubleKey, emptyOptions), null); + expect(await preferences.getStringList(listKey, emptyOptions), null); + }); + + testWidgets('clear with filter', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString, emptyOptions), + preferences.setBool(boolKey, testBool, emptyOptions), + preferences.setInt(intKey, testInt, emptyOptions), + preferences.setDouble(doubleKey, testDouble, emptyOptions), + preferences.setStringList(listKey, testList, emptyOptions) + ]); + await preferences.clear( + const ClearPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), ), + emptyOptions, ); - expect(values['String'], null); - expect(values['Bool'], null); - expect(values['Int'], null); - expect(values['Double'], null); - expect(values['StringList'], null); - expect(values['flutter.String'], null); - expect(values['flutter.Bool'], null); - expect(values['flutter.Int'], null); - expect(values['flutter.Double'], null); - expect(values['flutter.StringList'], null); + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + expect(await preferences.getStringList(listKey, emptyOptions), testList); }); }); - - testWidgets('simultaneous writes', (WidgetTester _) async { - final List> writes = >[]; - const int writeCount = 100; - for (int i = 1; i <= writeCount; i++) { - writes.add(preferences.setValue('Int', 'Int', i)); - } - final List result = await Future.wait(writes, eagerError: true); - // All writes should succeed. - expect(result.where((bool element) => !element), isEmpty); - // The last write should win. - final Map values = await preferences.getAllWithParameters( - GetAllParameters( - filter: PreferencesFilter(prefix: ''), - ), - ); - expect(values['Int'], writeCount); - }); } diff --git a/packages/shared_preferences/shared_preferences_web/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_web/example/pubspec.yaml index 44672c4e642..634e76da602 100644 --- a/packages/shared_preferences/shared_preferences_web/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_web/example/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - shared_preferences_platform_interface: ^2.3.0 + shared_preferences_platform_interface: ^2.4.0 shared_preferences_web: path: ../ web: ^0.5.0 diff --git a/packages/shared_preferences/shared_preferences_web/lib/shared_preferences_web.dart b/packages/shared_preferences/shared_preferences_web/lib/shared_preferences_web.dart index b83c1a5fe37..be75e1786bd 100644 --- a/packages/shared_preferences/shared_preferences_web/lib/shared_preferences_web.dart +++ b/packages/shared_preferences/shared_preferences_web/lib/shared_preferences_web.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:convert' show json; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; import 'package:shared_preferences_platform_interface/types.dart'; import 'package:web/web.dart' as html; @@ -19,6 +20,7 @@ class SharedPreferencesPlugin extends SharedPreferencesStorePlatform { /// Registers this class as the default instance of [SharedPreferencesStorePlatform]. static void registerWith(Registrar? registrar) { SharedPreferencesStorePlatform.instance = SharedPreferencesPlugin(); + SharedPreferencesAsyncWeb.registerWith(registrar); } static const String _defaultPrefix = 'flutter.'; @@ -44,7 +46,7 @@ class SharedPreferencesPlugin extends SharedPreferencesStorePlatform { // IMPORTANT: Do not use html.window.localStorage.clear() as that will // remove _all_ local data, not just the keys prefixed with // _prefix - _getFilteredKeys(filter.prefix, allowList: filter.allowList) + _getPrefixedKeys(filter.prefix, allowList: filter.allowList) .forEach(remove); return true; } @@ -70,7 +72,7 @@ class SharedPreferencesPlugin extends SharedPreferencesStorePlatform { final PreferencesFilter filter = parameters.filter; final Map allData = {}; for (final String key - in _getFilteredKeys(filter.prefix, allowList: filter.allowList)) { + in _getPrefixedKeys(filter.prefix, allowList: filter.allowList)) { allData[key] = _decodeValue(html.window.localStorage.getItem(key)!); } return allData; @@ -88,28 +90,189 @@ class SharedPreferencesPlugin extends SharedPreferencesStorePlatform { return true; } - Iterable _getFilteredKeys( + Iterable _getPrefixedKeys( String prefix, { Set? allowList, }) { - return html.window.localStorage.keys.where((String key) => - key.startsWith(prefix) && (allowList?.contains(key) ?? true)); + return _getAllowedKeys(allowList: allowList) + .where((String key) => key.startsWith(prefix)); } +} - String _encodeValue(Object? value) { - return json.encode(value); +/// The web implementation of [SharedPreferencesAsyncPlatform]. +/// +/// This class implements the `package:shared_preferences` functionality for the web. +base class SharedPreferencesAsyncWeb extends SharedPreferencesAsyncPlatform { + /// Registers this class as the default instance of [SharedPreferencesAsyncPlatform]. + static void registerWith(Registrar? registrar) { + SharedPreferencesAsyncPlatform.instance = SharedPreferencesAsyncWeb(); } - Object _decodeValue(String encodedValue) { - final Object? decodedValue = json.decode(encodedValue); + @override + Future clear( + ClearPreferencesParameters parameters, + SharedPreferencesOptions options, + ) async { + final PreferencesFilters filter = parameters.filter; + _getAllowedKeys(allowList: filter.allowList) + .forEach((String key) => html.window.localStorage.removeItem(key)); + } - if (decodedValue is List) { - // JSON does not preserve generics. The encode/decode roundtrip is - // `List` => JSON => `List`. We have to explicitly - // restore the RTTI. - return decodedValue.cast(); + @override + Future> getPreferences( + GetPreferencesParameters parameters, + SharedPreferencesOptions options, + ) async { + return _readAllFromLocalStorage(parameters.filter.allowList, options); + } + + Future> _readAllFromLocalStorage( + Set? allowList, + SharedPreferencesOptions options, + ) async { + final Map allData = {}; + for (final String key in _getAllowedKeys(allowList: allowList)) { + allData[key] = _decodeValue(html.window.localStorage.getItem(key)!); } + return allData; + } + + @override + Future> getKeys(GetPreferencesParameters parameters, + SharedPreferencesOptions options) async { + return (await getPreferences(parameters, options)).keys.toSet(); + } + + @override + Future setString( + String key, + String value, + SharedPreferencesOptions options, + ) { + return _setValue(key, value, options); + } + + @override + Future setBool( + String key, + bool value, + SharedPreferencesOptions options, + ) { + return _setValue(key, value, options); + } - return decodedValue!; + @override + Future setDouble( + String key, + double value, + SharedPreferencesOptions options, + ) { + return _setValue(key, value, options); + } + + @override + Future setInt( + String key, + int value, + SharedPreferencesOptions options, + ) { + return _setValue(key, value, options); + } + + @override + Future setStringList( + String key, + List value, + SharedPreferencesOptions options, + ) { + return _setValue(key, value, options); } + + Future _setValue( + String key, + Object? value, + SharedPreferencesOptions options, + ) async { + html.window.localStorage.setItem(key, _encodeValue(value)); + } + + @override + Future getString( + String key, + SharedPreferencesOptions options, + ) async { + final Map data = + await _readAllFromLocalStorage({key}, options); + return data[key] as String?; + } + + @override + Future getBool( + String key, + SharedPreferencesOptions options, + ) async { + final Map data = + await _readAllFromLocalStorage({key}, options); + return data[key] as bool?; + } + + @override + Future getDouble( + String key, + SharedPreferencesOptions options, + ) async { + final Map data = + await _readAllFromLocalStorage({key}, options); + return data[key] as double?; + } + + @override + Future getInt( + String key, + SharedPreferencesOptions options, + ) async { + final Map data = + await _readAllFromLocalStorage({key}, options); + return data[key] as int?; + } + + @override + Future?> getStringList( + String key, + SharedPreferencesOptions options, + ) async { + final Map data = + await _readAllFromLocalStorage({key}, options); + return data[key] as List?; + } +} + +Iterable _getAllowedKeys({ + Set? allowList, +}) { + return html.window.localStorage.keys + .where((String key) => allowList?.contains(key) ?? true); +} + +String _encodeValue(Object? value) { + return json.encode(value); +} + +Object _decodeValue(String encodedValue) { + final Object? decodedValue = json.decode(encodedValue); + + if (decodedValue is List) { + // JSON does not preserve generics. The encode/decode roundtrip is + // `List` => JSON => `List`. We have to explicitly + // restore the RTTI. + return decodedValue.cast(); + } + + return decodedValue!; +} + +/// Web specific SharedPreferences Options. +class SharedPreferencesWebOptions extends SharedPreferencesOptions { + /// Constructor for SharedPreferencesWebOptions. + const SharedPreferencesWebOptions(); } diff --git a/packages/shared_preferences/shared_preferences_web/pubspec.yaml b/packages/shared_preferences/shared_preferences_web/pubspec.yaml index 1278a54a3e5..375751af47b 100644 --- a/packages/shared_preferences/shared_preferences_web/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_web/pubspec.yaml @@ -2,7 +2,7 @@ name: shared_preferences_web description: Web platform implementation of shared_preferences repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.3.0 +version: 2.4.0 environment: sdk: ^3.3.0 @@ -21,7 +21,7 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - shared_preferences_platform_interface: ^2.3.0 + shared_preferences_platform_interface: ^2.4.0 web: ^0.5.0 dev_dependencies: diff --git a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md index 8137dbb765d..c5a859d69f7 100644 --- a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 2.4.0 +* Adds `SharedPreferencesAsyncWindows` API. * Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 2.3.2 diff --git a/packages/shared_preferences/shared_preferences_windows/example/integration_test/shared_preferences_test.dart b/packages/shared_preferences/shared_preferences_windows/example/integration_test/shared_preferences_test.dart index bd30ddeab4a..5549b9b5deb 100644 --- a/packages/shared_preferences/shared_preferences_windows/example/integration_test/shared_preferences_test.dart +++ b/packages/shared_preferences/shared_preferences_windows/example/integration_test/shared_preferences_test.dart @@ -4,6 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; import 'package:shared_preferences_platform_interface/types.dart'; import 'package:shared_preferences_windows/shared_preferences_windows.dart'; @@ -336,4 +337,187 @@ void main() { }); }); }); + + group('shared_preferences_async', () { + const SharedPreferencesWindowsOptions emptyOptions = + SharedPreferencesWindowsOptions(); + + const String stringKey = 'testString'; + const String boolKey = 'testBool'; + const String intKey = 'testInt'; + const String doubleKey = 'testDouble'; + const String listKey = 'testList'; + + const String testString = 'hello world'; + const bool testBool = true; + const int testInt = 42; + const double testDouble = 3.14159; + const List testList = ['foo', 'bar']; + + Future getPreferences() async { + final SharedPreferencesAsyncPlatform preferences = + SharedPreferencesAsyncPlatform.instance!; + await preferences.clear( + const ClearPreferencesParameters(filter: PreferencesFilters()), + emptyOptions); + return preferences; + } + + testWidgets('set and get String', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setString(stringKey, testString, emptyOptions); + expect(await preferences.getString(stringKey, emptyOptions), testString); + }); + + testWidgets('set and get bool', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setBool(boolKey, testBool, emptyOptions); + expect(await preferences.getBool(boolKey, emptyOptions), testBool); + }); + + testWidgets('set and get int', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setInt(intKey, testInt, emptyOptions); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + }); + + testWidgets('set and get double', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + }); + + testWidgets('set and get StringList', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + + await preferences.setStringList(listKey, testList, emptyOptions); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); + + testWidgets('getPreferences', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, + ); + + expect(gotAll.length, 5); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + expect(gotAll[intKey], testInt); + expect(gotAll[doubleKey], testDouble); + expect(gotAll[listKey], testList); + }); + + testWidgets('getPreferences with filter', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + + expect(gotAll.length, 2); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + }); + + testWidgets('getKeys', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, + ); + + expect(keys.length, 5); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + expect(keys, contains(intKey)); + expect(keys, contains(doubleKey)); + expect(keys, contains(listKey)); + }); + + testWidgets('getKeys with filter', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + + expect(keys.length, 2); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + }); + + testWidgets('clear', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + + await preferences.clear( + const ClearPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, + ); + + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), null); + expect(await preferences.getDouble(doubleKey, emptyOptions), null); + expect(await preferences.getStringList(listKey, emptyOptions), null); + }); + + testWidgets('clear with filter', (WidgetTester _) async { + final SharedPreferencesAsyncPlatform preferences = await getPreferences(); + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + await preferences.clear( + const ClearPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); + }); } diff --git a/packages/shared_preferences/shared_preferences_windows/example/lib/main.dart b/packages/shared_preferences/shared_preferences_windows/example/lib/main.dart index 3c5312aab60..fc07e68eb86 100644 --- a/packages/shared_preferences/shared_preferences_windows/example/lib/main.dart +++ b/packages/shared_preferences/shared_preferences_windows/example/lib/main.dart @@ -4,9 +4,8 @@ // ignore_for_file: public_member_api_docs -import 'dart:async'; - import 'package:flutter/material.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; import 'package:shared_preferences_windows/shared_preferences_windows.dart'; void main() { @@ -33,26 +32,36 @@ class SharedPreferencesDemo extends StatefulWidget { } class SharedPreferencesDemoState extends State { - final SharedPreferencesWindows prefs = SharedPreferencesWindows(); + final SharedPreferencesAsyncPlatform? _prefs = + SharedPreferencesAsyncPlatform.instance; + final SharedPreferencesWindowsOptions options = + const SharedPreferencesWindowsOptions(); + static const String _counterKey = 'counter'; late Future _counter; Future _incrementCounter() async { - final Map values = await prefs.getAll(); - final int counter = (values['counter'] as int? ?? 0) + 1; + final int? value = await _prefs!.getInt(_counterKey, options); + final int counter = (value ?? 0) + 1; setState(() { - _counter = prefs.setValue('Int', 'counter', counter).then((bool success) { + _counter = _prefs.setInt(_counterKey, counter, options).then((_) { return counter; }); }); } + Future _getAndSetCounter() async { + setState(() { + _counter = _prefs!.getInt(_counterKey, options).then((int? counter) { + return counter ?? 0; + }); + }); + } + @override void initState() { super.initState(); - _counter = prefs.getAll().then((Map values) { - return values['counter'] as int? ?? 0; - }); + _getAndSetCounter(); } @override diff --git a/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml index 8331a914fbf..302b5e414fc 100644 --- a/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - shared_preferences_platform_interface: ^2.3.0 + shared_preferences_platform_interface: ^2.4.0 shared_preferences_windows: # When depending on this package from a real application you should use: # shared_preferences_windows: ^x.y.z diff --git a/packages/shared_preferences/shared_preferences_windows/lib/shared_preferences_windows.dart b/packages/shared_preferences/shared_preferences_windows/lib/shared_preferences_windows.dart index 097e0a6c63c..0a2ec7688e7 100644 --- a/packages/shared_preferences/shared_preferences_windows/lib/shared_preferences_windows.dart +++ b/packages/shared_preferences/shared_preferences_windows/lib/shared_preferences_windows.dart @@ -10,9 +10,14 @@ import 'package:file/local.dart'; import 'package:flutter/foundation.dart' show debugPrint, visibleForTesting; import 'package:path/path.dart' as path; import 'package:path_provider_windows/path_provider_windows.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; import 'package:shared_preferences_platform_interface/types.dart'; +const String _defaultFileName = 'shared_preferences'; + +const String _defaultPrefix = 'flutter.'; + /// The Windows implementation of [SharedPreferencesStorePlatform]. /// /// This class implements the `package:shared_preferences` functionality for Windows. @@ -25,9 +30,12 @@ class SharedPreferencesWindows extends SharedPreferencesStorePlatform { /// Registers the Windows implementation. static void registerWith() { SharedPreferencesStorePlatform.instance = SharedPreferencesWindows(); + // A temporary work-around for having two plugins contained in a single package. + SharedPreferencesAsyncWindows.registerWith(); } - static const String _defaultPrefix = 'flutter.'; + /// Local copy of preferences + Map? _cachedPreferences; /// File system used to store to disk. Exposed for testing only. @visibleForTesting @@ -37,66 +45,15 @@ class SharedPreferencesWindows extends SharedPreferencesStorePlatform { @visibleForTesting PathProviderWindows pathProvider = PathProviderWindows(); - /// Local copy of preferences - Map? _cachedPreferences; - - /// Cached file for storing preferences. - File? _localDataFilePath; - - /// Gets the file where the preferences are stored. - Future _getLocalDataFile() async { - if (_localDataFilePath != null) { - return _localDataFilePath!; - } - final String? directory = await pathProvider.getApplicationSupportPath(); - if (directory == null) { - return null; - } - return _localDataFilePath = - fs.file(path.join(directory, 'shared_preferences.json')); - } - - /// Gets the preferences from the stored file. Once read, the preferences are - /// maintained in memory. - Future> _reload() async { - Map preferences = {}; - final File? localDataFile = await _getLocalDataFile(); - if (localDataFile != null && localDataFile.existsSync()) { - final String stringMap = localDataFile.readAsStringSync(); - if (stringMap.isNotEmpty) { - final Object? data = json.decode(stringMap); - if (data is Map) { - preferences = data.cast(); - } - } - } - _cachedPreferences = preferences; - return preferences; - } - + /// Checks for cached preferences and returns them or loads preferences from + /// file and returns and caches them. Future> _readPreferences() async { - return _cachedPreferences ?? await _reload(); - } - - /// Writes the cached preferences to disk. Returns [true] if the operation - /// succeeded. - Future _writePreferences(Map preferences) async { - try { - final File? localDataFile = await _getLocalDataFile(); - if (localDataFile == null) { - debugPrint('Unable to determine where to write preferences.'); - return false; - } - if (!localDataFile.existsSync()) { - localDataFile.createSync(recursive: true); - } - final String stringMap = json.encode(preferences); - localDataFile.writeAsStringSync(stringMap); - } catch (e) { - debugPrint('Error saving preferences to disk: $e'); - return false; - } - return true; + _cachedPreferences ??= await _readFromFile( + _defaultFileName, + fs: fs, + pathProvider: pathProvider, + ); + return _cachedPreferences!; } @override @@ -117,11 +74,17 @@ class SharedPreferencesWindows extends SharedPreferencesStorePlatform { @override Future clearWithParameters(ClearParameters parameters) async { final PreferencesFilter filter = parameters.filter; + final Map preferences = await _readPreferences(); preferences.removeWhere((String key, _) => key.startsWith(filter.prefix) && (filter.allowList == null || filter.allowList!.contains(key))); - return _writePreferences(preferences); + return _writePreferences( + preferences, + _defaultFileName, + fs: fs, + pathProvider: pathProvider, + ); } @override @@ -154,13 +117,297 @@ class SharedPreferencesWindows extends SharedPreferencesStorePlatform { Future remove(String key) async { final Map preferences = await _readPreferences(); preferences.remove(key); - return _writePreferences(preferences); + return _writePreferences( + preferences, + _defaultFileName, + fs: fs, + pathProvider: pathProvider, + ); } @override Future setValue(String valueType, String key, Object value) async { final Map preferences = await _readPreferences(); preferences[key] = value; - return _writePreferences(preferences); + return _writePreferences( + preferences, + _defaultFileName, + fs: fs, + pathProvider: pathProvider, + ); + } +} + +/// The Windows implementation of [SharedPreferencesAsyncPlatform]. +/// +/// This class implements the `package:shared_preferences` functionality for Windows. +base class SharedPreferencesAsyncWindows + extends SharedPreferencesAsyncPlatform { + /// Registers the Windows implementation. + static void registerWith() { + SharedPreferencesAsyncPlatform.instance = SharedPreferencesAsyncWindows(); + } + + /// Local copy of preferences + Map? _cachedPreferences; + + /// File system used to store to disk. Exposed for testing only. + @visibleForTesting + FileSystem fs = const LocalFileSystem(); + + /// The path_provider_windows instance used to find the support directory. + @visibleForTesting + PathProviderWindows pathProvider = PathProviderWindows(); + + @override + Future> getKeys( + GetPreferencesParameters parameters, + SharedPreferencesOptions options, + ) async { + return (await getPreferences(parameters, options)).keys.toSet(); + } + + @override + Future setString( + String key, + String value, + SharedPreferencesOptions options, + ) { + return _setValue(key, value, options); + } + + @override + Future setBool( + String key, + bool value, + SharedPreferencesOptions options, + ) { + return _setValue(key, value, options); + } + + @override + Future setDouble( + String key, + double value, + SharedPreferencesOptions options, + ) { + return _setValue(key, value, options); + } + + @override + Future setInt( + String key, + int value, + SharedPreferencesOptions options, + ) { + return _setValue(key, value, options); + } + + @override + Future setStringList( + String key, + List value, + SharedPreferencesOptions options, + ) { + return _setValue(key, value, options); + } + + @override + Future getString( + String key, + SharedPreferencesOptions options, + ) async { + final Map data = await _readAll({key}, options); + return data[key] as String?; + } + + @override + Future getBool( + String key, + SharedPreferencesOptions options, + ) async { + final Map data = await _readAll({key}, options); + return data[key] as bool?; + } + + @override + Future getDouble( + String key, + SharedPreferencesOptions options, + ) async { + final Map data = await _readAll({key}, options); + return data[key] as double?; + } + + @override + Future getInt( + String key, + SharedPreferencesOptions options, + ) async { + final Map data = await _readAll({key}, options); + return data[key] as int?; + } + + @override + Future?> getStringList( + String key, + SharedPreferencesOptions options, + ) async { + final Map data = await _readAll({key}, options); + return (data[key] as List?)?.toList(); + } + + @override + Future clear(ClearPreferencesParameters parameters, + SharedPreferencesOptions options) async { + final SharedPreferencesWindowsOptions windowsOptions = + SharedPreferencesWindowsOptions.fromSharedPreferencesOptions(options); + final PreferencesFilters filter = parameters.filter; + final Map preferences = + await _readPreferences(windowsOptions.fileName); + preferences.removeWhere((String key, _) => + filter.allowList == null || filter.allowList!.contains(key)); + await _writePreferences( + preferences, + windowsOptions.fileName, + fs: fs, + pathProvider: pathProvider, + ); + } + + @override + Future> getPreferences( + GetPreferencesParameters parameters, + SharedPreferencesOptions options, + ) async { + return _readAll(parameters.filter.allowList, options); + } + + Future> _readAll( + Set? allowList, + SharedPreferencesOptions options, + ) async { + final SharedPreferencesWindowsOptions windowsOptions = + SharedPreferencesWindowsOptions.fromSharedPreferencesOptions(options); + final Map prefs = Map.from( + await _readPreferences(windowsOptions.fileName)); + prefs.removeWhere((String key, _) => !(allowList?.contains(key) ?? true)); + return prefs; + } + + Future _setValue( + String key, Object value, SharedPreferencesOptions options) async { + final SharedPreferencesWindowsOptions windowsOptions = + SharedPreferencesWindowsOptions.fromSharedPreferencesOptions(options); + final Map preferences = + await _readPreferences(windowsOptions.fileName); + preferences[key] = value; + await _writePreferences( + preferences, + windowsOptions.fileName, + fs: fs, + pathProvider: pathProvider, + ); + } + + /// Checks for cached preferences and returns them or loads preferences from + /// file and returns and caches them. + Future> _readPreferences(String fileName) async { + _cachedPreferences ??= await _readFromFile( + fileName, + fs: fs, + pathProvider: pathProvider, + ); + return _cachedPreferences!; + } +} + +/// Gets the file where the preferences are stored. +Future _getLocalDataFile( + String fileName, { + FileSystem fs = const LocalFileSystem(), + PathProviderWindows? pathProvider, +}) async { + pathProvider = pathProvider ?? PathProviderWindows(); + final String? directory = await pathProvider.getApplicationSupportPath(); + if (directory == null) { + return null; + } + final String fileLocation = path.join(directory, '$fileName.json'); + return fs.file(fileLocation); +} + +/// Gets the preferences from the stored file. +Future> _readFromFile( + String fileName, { + FileSystem fs = const LocalFileSystem(), + PathProviderWindows? pathProvider, +}) async { + Map preferences = {}; + final File? localDataFile = await _getLocalDataFile( + fileName, + fs: fs, + pathProvider: pathProvider, + ); + if (localDataFile != null && localDataFile.existsSync()) { + final String stringMap = localDataFile.readAsStringSync(); + if (stringMap.isNotEmpty) { + final Object? data = json.decode(stringMap); + if (data is Map) { + preferences = data.cast(); + } + } + } + return preferences; +} + +/// Writes the cached preferences to disk. Returns [true] if the operation +/// succeeded. +Future _writePreferences( + Map preferences, + String fileName, { + FileSystem fs = const LocalFileSystem(), + PathProviderWindows? pathProvider, +}) async { + try { + final File? localDataFile = await _getLocalDataFile( + fileName, + fs: fs, + pathProvider: pathProvider, + ); + if (localDataFile == null) { + debugPrint('Unable to determine where to write preferences.'); + return false; + } + if (!localDataFile.existsSync()) { + localDataFile.createSync(recursive: true); + } + final String stringMap = json.encode(preferences); + localDataFile.writeAsStringSync(stringMap); + } catch (e) { + debugPrint('Error saving preferences to disk: $e'); + return false; + } + return true; +} + +/// Windows specific SharedPreferences Options. +class SharedPreferencesWindowsOptions extends SharedPreferencesOptions { + /// Constructor for SharedPreferencesWindowsOptions. + const SharedPreferencesWindowsOptions({ + this.fileName = 'shared_preferences', // Same as current defaults. + }); + + /// The name of the file to store preferences in. + final String fileName; + + /// Returns a new instance of [SharedPreferencesWindowsOptions] from an existing + /// [SharedPreferencesOptions]. + static SharedPreferencesWindowsOptions fromSharedPreferencesOptions( + SharedPreferencesOptions options) { + if (options is SharedPreferencesWindowsOptions) { + return options; + } + return const SharedPreferencesWindowsOptions(); } } diff --git a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml index b8481f59830..653fb51341a 100644 --- a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml @@ -2,7 +2,7 @@ name: shared_preferences_windows description: Windows implementation of shared_preferences repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.3.2 +version: 2.4.0 environment: sdk: ^3.2.0 @@ -22,7 +22,7 @@ dependencies: path: ^1.8.0 path_provider_platform_interface: ^2.0.0 path_provider_windows: ^2.0.0 - shared_preferences_platform_interface: ^2.3.0 + shared_preferences_platform_interface: ^2.4.0 dev_dependencies: flutter_test: diff --git a/packages/shared_preferences/shared_preferences_windows/test/fake_path_provider_windows.dart b/packages/shared_preferences/shared_preferences_windows/test/fake_path_provider_windows.dart new file mode 100644 index 00000000000..8b7b374118e --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/test/fake_path_provider_windows.dart @@ -0,0 +1,36 @@ +// 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:flutter_test/flutter_test.dart'; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; +import 'package:path_provider_windows/path_provider_windows.dart'; + +/// Fake implementation of PathProviderWindows that returns hard-coded paths, +/// allowing tests to run on any platform. +/// +/// Note that this should only be used with an in-memory filesystem, as the +/// path it returns is a root path that does not actually exist on Windows. +class FakePathProviderWindows extends PathProviderPlatform + implements PathProviderWindows { + @override + late VersionInfoQuerier versionInfoQuerier; + + @override + Future getApplicationSupportPath() async => r'C:\appsupport'; + + @override + Future getTemporaryPath() async => null; + + @override + Future getLibraryPath() async => null; + + @override + Future getApplicationDocumentsPath() async => null; + + @override + Future getDownloadsPath() async => null; + + @override + Future getPath(String folderID) async => ''; +} diff --git a/packages/shared_preferences/shared_preferences_windows/test/shared_preferences_windows_test.dart b/packages/shared_preferences/shared_preferences_windows/test/legacy_shared_preferences_windows_test.dart similarity index 89% rename from packages/shared_preferences/shared_preferences_windows/test/shared_preferences_windows_test.dart rename to packages/shared_preferences/shared_preferences_windows/test/legacy_shared_preferences_windows_test.dart index 499a74eeca3..6a8de0aaf4e 100644 --- a/packages/shared_preferences/shared_preferences_windows/test/shared_preferences_windows_test.dart +++ b/packages/shared_preferences/shared_preferences_windows/test/legacy_shared_preferences_windows_test.dart @@ -7,12 +7,13 @@ import 'dart:convert'; import 'package:file/memory.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart' as path; -import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; import 'package:path_provider_windows/path_provider_windows.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; import 'package:shared_preferences_platform_interface/types.dart'; import 'package:shared_preferences_windows/shared_preferences_windows.dart'; +import 'fake_path_provider_windows.dart'; + void main() { late MemoryFileSystem fs; late PathProviderWindows pathProvider; @@ -76,7 +77,7 @@ void main() { return prefs; } - test('registered instance', () { + test('registered instance', () async { SharedPreferencesWindows.registerWith(); expect(SharedPreferencesStorePlatform.instance, isA()); @@ -255,32 +256,3 @@ void main() { expect(noValues, hasLength(0)); }); } - -/// Fake implementation of PathProviderWindows that returns hard-coded paths, -/// allowing tests to run on any platform. -/// -/// Note that this should only be used with an in-memory filesystem, as the -/// path it returns is a root path that does not actually exist on Windows. -class FakePathProviderWindows extends PathProviderPlatform - implements PathProviderWindows { - @override - late VersionInfoQuerier versionInfoQuerier; - - @override - Future getApplicationSupportPath() async => r'C:\appsupport'; - - @override - Future getTemporaryPath() async => null; - - @override - Future getLibraryPath() async => null; - - @override - Future getApplicationDocumentsPath() async => null; - - @override - Future getDownloadsPath() async => null; - - @override - Future getPath(String folderID) async => ''; -} diff --git a/packages/shared_preferences/shared_preferences_windows/test/shared_preferences_windows_async_test.dart b/packages/shared_preferences/shared_preferences_windows/test/shared_preferences_windows_async_test.dart new file mode 100755 index 00000000000..6846f1fa995 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/test/shared_preferences_windows_async_test.dart @@ -0,0 +1,197 @@ +// 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:file/memory.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:path_provider_windows/path_provider_windows.dart'; +import 'package:shared_preferences_platform_interface/types.dart'; +import 'package:shared_preferences_windows/shared_preferences_windows.dart'; + +import 'fake_path_provider_windows.dart'; + +void main() { + late MemoryFileSystem fs; + late PathProviderWindows pathProvider; + + SharedPreferencesAsyncWindows.registerWith(); + + const String stringKey = 'testString'; + const String boolKey = 'testBool'; + const String intKey = 'testInt'; + const String doubleKey = 'testDouble'; + const String listKey = 'testList'; + + const String testString = 'hello world'; + const bool testBool = true; + const int testInt = 42; + const double testDouble = 3.14159; + const List testList = ['foo', 'bar']; + + const SharedPreferencesWindowsOptions emptyOptions = + SharedPreferencesWindowsOptions(); + + setUp(() { + fs = MemoryFileSystem.test(); + pathProvider = FakePathProviderWindows(); + }); + + SharedPreferencesAsyncWindows getPreferences() { + final SharedPreferencesAsyncWindows prefs = SharedPreferencesAsyncWindows(); + prefs.fs = fs; + prefs.pathProvider = pathProvider; + return prefs; + } + + test('set and get String', () async { + final SharedPreferencesAsyncWindows preferences = getPreferences(); + + await preferences.setString(stringKey, testString, emptyOptions); + expect(await preferences.getString(stringKey, emptyOptions), testString); + }); + + test('set and get bool', () async { + final SharedPreferencesAsyncWindows preferences = getPreferences(); + + await preferences.setBool(boolKey, testBool, emptyOptions); + expect(await preferences.getBool(boolKey, emptyOptions), testBool); + }); + + test('set and get int', () async { + final SharedPreferencesAsyncWindows preferences = getPreferences(); + + await preferences.setInt(intKey, testInt, emptyOptions); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + }); + + test('set and get double', () async { + final SharedPreferencesAsyncWindows preferences = getPreferences(); + + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + }); + + test('set and get StringList', () async { + final SharedPreferencesAsyncWindows preferences = getPreferences(); + + await preferences.setStringList(listKey, testList, emptyOptions); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); + + test('getPreferences', () async { + final SharedPreferencesAsyncWindows preferences = getPreferences(); + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions); + + expect(gotAll.length, 5); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + expect(gotAll[intKey], testInt); + expect(gotAll[doubleKey], testDouble); + expect(gotAll[listKey], testList); + }); + + test('getPreferences with filter', () async { + final SharedPreferencesAsyncWindows preferences = getPreferences(); + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + + final Map gotAll = await preferences.getPreferences( + const GetPreferencesParameters( + filter: + PreferencesFilters(allowList: {stringKey, boolKey})), + emptyOptions); + + expect(gotAll.length, 2); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + }); + + test('getKeys', () async { + final SharedPreferencesAsyncWindows preferences = getPreferences(); + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters(filter: PreferencesFilters()), + emptyOptions, + ); + + expect(keys.length, 5); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + expect(keys, contains(intKey)); + expect(keys, contains(doubleKey)); + expect(keys, contains(listKey)); + }); + + test('getKeys with filter', () async { + final SharedPreferencesAsyncWindows preferences = getPreferences(); + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + + final Set keys = await preferences.getKeys( + const GetPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + + expect(keys.length, 2); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + }); + + test('clear', () async { + final SharedPreferencesAsyncWindows preferences = getPreferences(); + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + await preferences.clear( + const ClearPreferencesParameters(filter: PreferencesFilters()), + emptyOptions); + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), null); + expect(await preferences.getDouble(doubleKey, emptyOptions), null); + expect(await preferences.getStringList(listKey, emptyOptions), null); + }); + + test('clear with filter', () async { + final SharedPreferencesAsyncWindows preferences = getPreferences(); + await preferences.setString(stringKey, testString, emptyOptions); + await preferences.setBool(boolKey, testBool, emptyOptions); + await preferences.setInt(intKey, testInt, emptyOptions); + await preferences.setDouble(doubleKey, testDouble, emptyOptions); + await preferences.setStringList(listKey, testList, emptyOptions); + await preferences.clear( + const ClearPreferencesParameters( + filter: PreferencesFilters(allowList: {stringKey, boolKey}), + ), + emptyOptions, + ); + expect(await preferences.getString(stringKey, emptyOptions), null); + expect(await preferences.getBool(boolKey, emptyOptions), null); + expect(await preferences.getInt(intKey, emptyOptions), testInt); + expect(await preferences.getDouble(doubleKey, emptyOptions), testDouble); + expect(await preferences.getStringList(listKey, emptyOptions), testList); + }); +}