Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2.4.4

* Restores the behavior of throwing a `TypeError` when calling `getStringList`
on a value stored with `setString`.

## 2.4.3

* Migrates `List<String>` value encoding to JSON.
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (v22.7.2), do not edit directly.
// Autogenerated from Pigeon (v22.7.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")

Expand Down Expand Up @@ -61,6 +61,39 @@ data class SharedPreferencesPigeonOptions(val fileName: String? = null, val useD
}
}

/** Generated class from Pigeon that represents data sent in messages. */
data class StringListResult(
/**
* The JSON-encoded stored value, or null if there isn't one.
*
* This will be null if either there is no store value (in which case
* [foundPlatformEncodedValue] will be false), or if there was a platform-encoded value.
*/
val jsonEncodedValue: String? = null,
/**
* Whether value using the legacy platform-side encoding was found.
*
* If this is true, [jsonEncodedValue] will be null, and the value should be fetched with
* getPlatformEncodedStringList(...) instead.
*/
val foundPlatformEncodedValue: Boolean
) {
companion object {
fun fromList(pigeonVar_list: List<Any?>): StringListResult {
val jsonEncodedValue = pigeonVar_list[0] as String?
val foundPlatformEncodedValue = pigeonVar_list[1] as Boolean
return StringListResult(jsonEncodedValue, foundPlatformEncodedValue)
}
}

fun toList(): List<Any?> {
return listOf(
jsonEncodedValue,
foundPlatformEncodedValue,
)
}
}

private open class MessagesAsyncPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
Expand All @@ -69,6 +102,9 @@ private open class MessagesAsyncPigeonCodec : StandardMessageCodec() {
SharedPreferencesPigeonOptions.fromList(it)
}
}
130.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { StringListResult.fromList(it) }
}
else -> super.readValueOfType(type, buffer)
}
}
Expand All @@ -79,6 +115,10 @@ private open class MessagesAsyncPigeonCodec : StandardMessageCodec() {
stream.write(129)
writeValue(stream, value.toList())
}
is StringListResult -> {
stream.write(130)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
Expand Down Expand Up @@ -119,8 +159,8 @@ interface SharedPreferencesAsyncApi {
key: String,
options: SharedPreferencesPigeonOptions
): List<String>?
/** Gets individual List<String> value stored with [key], if any. */
fun getStringList(key: String, options: SharedPreferencesPigeonOptions): String?
/** Gets the JSON-encoded List<String> value stored with [key], if any. */
fun getStringList(key: String, options: SharedPreferencesPigeonOptions): StringListResult?
/** Removes all properties from shared preferences data set with matching prefix. */
fun clear(allowList: List<String>?, options: SharedPreferencesPigeonOptions)
/** Gets all properties from shared preferences data set with matching prefix. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,20 @@ class SharedPreferencesPlugin() : FlutterPlugin, SharedPreferencesAsyncApi {
}

/** Gets StringList at [key] from data store. */
override fun getStringList(key: String, options: SharedPreferencesPigeonOptions): String? {
override fun getStringList(
key: String,
options: SharedPreferencesPigeonOptions
): StringListResult? {
val stringValue = getString(key, options)
stringValue?.let {
// The JSON-encoded lists use an extended prefix to distinguish them from
// lists that using listEncoder.
if (stringValue.startsWith(JSON_LIST_PREFIX)) {
return stringValue
return if (stringValue.startsWith(JSON_LIST_PREFIX)) {
StringListResult(stringValue, foundPlatformEncodedValue = false)
} else if (stringValue.startsWith(LIST_PREFIX)) {
StringListResult(null, foundPlatformEncodedValue = true)
} else {
StringListResult(null, foundPlatformEncodedValue = false)
}
}
return null
Expand Down Expand Up @@ -408,12 +415,21 @@ class SharedPreferencesBackend(
}

/** Gets StringList at [key] from data store. */
override fun getStringList(key: String, options: SharedPreferencesPigeonOptions): String? {
override fun getStringList(
key: String,
options: SharedPreferencesPigeonOptions
): StringListResult? {
val preferences = createSharedPreferences(options)
if (preferences.contains(key)) {
val value = preferences.getString(key, "")
if (value!!.startsWith(JSON_LIST_PREFIX)) {
return value
// The JSON-encoded lists use an extended prefix to distinguish them from
// lists that using listEncoder.
return if (value!!.startsWith(JSON_LIST_PREFIX)) {
StringListResult(value, foundPlatformEncodedValue = false)
} else if (value.startsWith(LIST_PREFIX)) {
StringListResult(null, foundPlatformEncodedValue = true)
} else {
StringListResult(null, foundPlatformEncodedValue = false)
}
}
return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,26 @@ internal class SharedPreferencesTest {
fun testSetAndGetStringListWithDataStore() {
val plugin = pluginSetup(dataStoreOptions)
plugin.setEncodedStringList(listKey, testList, dataStoreOptions)
Assert.assertEquals(plugin.getStringList(listKey, dataStoreOptions), testList)
val result = plugin.getStringList(listKey, dataStoreOptions)
Assert.assertEquals(result?.jsonEncodedValue, testList)
}

@Test
fun testSetAndGetStringListWithDataStoreRedirectsForLegacy() {
val plugin = pluginSetup(dataStoreOptions)
plugin.setDeprecatedStringList(listKey, listOf(""), dataStoreOptions)
val result = plugin.getStringList(listKey, dataStoreOptions)
Assert.assertEquals(result?.jsonEncodedValue, null)
Assert.assertEquals(result?.foundPlatformEncodedValue, true)
}

@Test
fun testSetAndGetStringListWithDataStoreReportsRawString() {
val plugin = pluginSetup(dataStoreOptions)
plugin.setString(listKey, testString, dataStoreOptions)
val result = plugin.getStringList(listKey, dataStoreOptions)
Assert.assertEquals(result?.jsonEncodedValue, null)
Assert.assertEquals(result?.foundPlatformEncodedValue, false)
}

@Test
Expand Down Expand Up @@ -217,7 +236,26 @@ internal class SharedPreferencesTest {
fun testSetAndGetStringListWithSharedPreferences() {
val plugin = pluginSetup(sharedPreferencesOptions)
plugin.setEncodedStringList(listKey, testList, sharedPreferencesOptions)
Assert.assertEquals(plugin.getStringList(listKey, sharedPreferencesOptions), testList)
val result = plugin.getStringList(listKey, sharedPreferencesOptions)
Assert.assertEquals(result?.jsonEncodedValue, testList)
}

@Test
fun testSetAndGetStringListWithSharedPreferencesRedirectsForLegacy() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non blocking nit: would prefer "...DeprecatedStringList" over "Legacy" but this is a test so the consequence is small.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to the more specific "PlatformEncoded".

val plugin = pluginSetup(sharedPreferencesOptions)
plugin.setDeprecatedStringList(listKey, listOf(""), sharedPreferencesOptions)
val result = plugin.getStringList(listKey, sharedPreferencesOptions)
Assert.assertEquals(result?.jsonEncodedValue, null)
Assert.assertEquals(result?.foundPlatformEncodedValue, true)
}

@Test
fun testSetAndGetStringListWithSharedPreferencesReportsRawString() {
val plugin = pluginSetup(sharedPreferencesOptions)
plugin.setString(listKey, testString, sharedPreferencesOptions)
val result = plugin.getStringList(listKey, sharedPreferencesOptions)
Assert.assertEquals(result?.jsonEncodedValue, null)
Assert.assertEquals(result?.foundPlatformEncodedValue, false)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,22 @@ void main() {
expect(list?.length, testList.length + 1);
});

testWidgets('getStringList throws type error for String with $backend',
(WidgetTester _) async {
final SharedPreferencesAsyncAndroidOptions options =
getOptions(useDataStore: useDataStore, fileName: 'notDefault');
final SharedPreferencesAsyncPlatform preferences = getPreferences();
await clearPreferences(preferences, options);

await preferences.setString(listKey, testString, options);

// Internally, List<String> is stored as a String on Android, but that
// implementation detail shouldn't leak to clients; getting the wrong
// type should still throw.
expect(preferences.getStringList(listKey, options),
throwsA(isA<TypeError>()));
});

testWidgets('getPreferences with $backend', (WidgetTester _) async {
final SharedPreferencesAsyncAndroidOptions options =
getOptions(useDataStore: useDataStore, fileName: 'notDefault');
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (v22.7.2), do not edit directly.
// Autogenerated from Pigeon (v22.7.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, no_leading_underscores_for_local_identifiers

Expand Down Expand Up @@ -44,6 +44,41 @@ class SharedPreferencesPigeonOptions {
}
}

class StringListResult {
StringListResult({
this.jsonEncodedValue,
required this.foundPlatformEncodedValue,
});

/// The JSON-encoded stored value, or null if there isn't one.
///
/// This will be null if either there is no store value (in which case
/// [foundPlatformEncodedValue] will be false), or if there was a
/// platform-encoded value.
String? jsonEncodedValue;

/// Whether value using the legacy platform-side encoding was found.
///
/// If this is true, [jsonEncodedValue] will be null, and the value should be
/// fetched with getPlatformEncodedStringList(...) instead.
bool foundPlatformEncodedValue;

Object encode() {
return <Object?>[
jsonEncodedValue,
foundPlatformEncodedValue,
];
}

static StringListResult decode(Object result) {
result as List<Object?>;
return StringListResult(
jsonEncodedValue: result[0] as String?,
foundPlatformEncodedValue: result[1]! as bool,
);
}
}

class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec();
@override
Expand All @@ -54,6 +89,9 @@ class _PigeonCodec extends StandardMessageCodec {
} else if (value is SharedPreferencesPigeonOptions) {
buffer.putUint8(129);
writeValue(buffer, value.encode());
} else if (value is StringListResult) {
buffer.putUint8(130);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
Expand All @@ -64,6 +102,8 @@ class _PigeonCodec extends StandardMessageCodec {
switch (type) {
case 129:
return SharedPreferencesPigeonOptions.decode(readValue(buffer)!);
case 130:
return StringListResult.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
}
Expand Down Expand Up @@ -373,8 +413,8 @@ class SharedPreferencesAsyncApi {
}
}

/// Gets individual List<String> value stored with [key], if any.
Future<String?> getStringList(
/// Gets the JSON-encoded List<String> value stored with [key], if any.
Future<StringListResult?> getStringList(
String key, SharedPreferencesPigeonOptions options) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.shared_preferences_android.SharedPreferencesAsyncApi.getStringList$pigeonVar_messageChannelSuffix';
Expand All @@ -395,7 +435,7 @@ class SharedPreferencesAsyncApi {
details: pigeonVar_replyList[2],
);
} else {
return (pigeonVar_replyList[0] as String?);
return (pigeonVar_replyList[0] as StringListResult?);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,13 @@ base class SharedPreferencesAsyncAndroid
convertOptionsToPigeonOptions(options);
final SharedPreferencesAsyncApi api = getApiForBackend(pigeonOptions);
// Request JSON encoded string list.
final String? jsonEncodedStringList =
await _convertKnownExceptions<String?>(
final StringListResult? result =
await _convertKnownExceptions<StringListResult?>(
() async => api.getStringList(key, pigeonOptions));
if (result == null) {
return null;
}
final String? jsonEncodedStringList = result.jsonEncodedValue;
if (jsonEncodedStringList != null) {
final String jsonEncodedString =
jsonEncodedStringList.substring(jsonListPrefix.length);
Expand All @@ -213,10 +217,16 @@ base class SharedPreferencesAsyncAndroid
}
}
// If no JSON encoded string list exists, check for platform encoded value.
final List<String>? stringList =
await _convertKnownExceptions<List<String>?>(
() async => api.getPlatformEncodedStringList(key, pigeonOptions));
return stringList?.cast<String>().toList();
if (result.foundPlatformEncodedValue) {
final List<String>? stringList =
await _convertKnownExceptions<List<String>?>(
() async => api.getPlatformEncodedStringList(key, pigeonOptions));
return stringList?.cast<String>().toList();
} else {
// A non-null result where foundPlatformEncodedValue is false means there
// was a raw string result, so the client is fetching the wrong type.
throw TypeError();
}
}

Future<T?> _convertKnownExceptions<T>(Future<T?> Function() method) async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,27 @@ class SharedPreferencesPigeonOptions {
bool useDataStore;
}

class StringListResult {
StringListResult({
required this.jsonEncodedValue,
required this.foundPlatformEncodedValue,
});

/// The JSON-encoded stored value, or null if something else was found, in
/// which case [foundPlatformEncodedValue] will indicate its type.
String? jsonEncodedValue;

/// Whether value using the legacy platform-side encoding was found.
///
/// This value is only meaningful if [jsonEncodedValue] is null.
/// - If true, the value should be fetched with
/// getPlatformEncodedStringList(...) instead.
/// - If false, an unexpected string (one without any encoding prefix) was
/// found. This will happen if a client uses getStringList with a key that
/// was used with setString.
bool foundPlatformEncodedValue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like the type of decision we will regret later when there is yet another way to store lists of strings. Consider an enum.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like the type of decision we will regret later when there is yet another way to store lists of strings.

I did consider an enum originally, but the two-value enum seemed like overkill once I started writing it. I'll go ahead and switch back, but this is an internal implementation detail, so if ($DIETY forbid) we add a third way to encode string lists we wouldn't regret the bool, we would just change it then.

(Also, ideally we'd refactor more logic into Dart before then so we wouldn't even need to.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed; it's now a three-state enum. One of the states is redundant with the string value being non-null, but that made the logic flow easier to understand than it was before.

}

@HostApi(dartHostTestHandler: 'TestSharedPreferencesAsyncApi')
abstract class SharedPreferencesAsyncApi {
/// Adds property to shared preferences data set of type bool.
Expand Down Expand Up @@ -107,9 +128,9 @@ abstract class SharedPreferencesAsyncApi {
SharedPreferencesPigeonOptions options,
);

/// Gets individual List<String> value stored with [key], if any.
/// Gets the JSON-encoded List<String> value stored with [key], if any.
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
String? getStringList(
StringListResult? getStringList(
String key,
SharedPreferencesPigeonOptions options,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: shared_preferences_android
description: Android implementation of the shared_preferences plugin
repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22
version: 2.4.3
version: 2.4.4

environment:
sdk: ^3.5.0
Expand Down
Loading