Skip to content

[shared_preferences] Fix Android type mismatch regression #8512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -43,6 +43,22 @@ class SharedPreferencesError(
val details: Any? = null
) : Throwable()

/** Possible types found during a getStringList call. */
enum class StringListLookupResultType(val raw: Int) {
/** A deprecated platform-side encoding string list. */
PLATFORM_ENCODED(0),
/** A JSON-encoded string list. */
JSON_ENCODED(1),
/** A string that doesn't have the expected encoding prefix. */
UNEXPECTED_STRING(2);

companion object {
fun ofRaw(raw: Int): StringListLookupResultType? {
return values().firstOrNull { it.raw == raw }
}
}
}

/** Generated class from Pigeon that represents data sent in messages. */
data class SharedPreferencesPigeonOptions(val fileName: String? = null, val useDataStore: Boolean) {
companion object {
Expand All @@ -61,22 +77,59 @@ 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 something else was found. */
val jsonEncodedValue: String? = null,
/** The type of value found. */
val type: StringListLookupResultType
) {
companion object {
fun fromList(pigeonVar_list: List<Any?>): StringListResult {
val jsonEncodedValue = pigeonVar_list[0] as String?
val type = pigeonVar_list[1] as StringListLookupResultType
return StringListResult(jsonEncodedValue, type)
}
}

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

private open class MessagesAsyncPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as Long?)?.let { StringListLookupResultType.ofRaw(it.toInt()) }
}
130.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
SharedPreferencesPigeonOptions.fromList(it)
}
}
131.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { StringListResult.fromList(it) }
}
else -> super.readValueOfType(type, buffer)
}
}

override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is SharedPreferencesPigeonOptions -> {
is StringListLookupResultType -> {
stream.write(129)
writeValue(stream, value.raw)
}
is SharedPreferencesPigeonOptions -> {
stream.write(130)
writeValue(stream, value.toList())
}
is StringListResult -> {
stream.write(131)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
Expand Down Expand Up @@ -119,8 +172,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, StringListLookupResultType.JSON_ENCODED)
} else if (stringValue.startsWith(LIST_PREFIX)) {
StringListResult(null, StringListLookupResultType.PLATFORM_ENCODED)
} else {
StringListResult(null, StringListLookupResultType.UNEXPECTED_STRING)
}
}
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, StringListLookupResultType.JSON_ENCODED)
} else if (value.startsWith(LIST_PREFIX)) {
StringListResult(null, StringListLookupResultType.PLATFORM_ENCODED)
} else {
StringListResult(null, StringListLookupResultType.UNEXPECTED_STRING)
}
}
return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,27 @@ 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)
Assert.assertEquals(result?.type, StringListLookupResultType.JSON_ENCODED)
}

@Test
fun testSetAndGetStringListWithDataStoreRedirectsForPlatformEncoded() {
val plugin = pluginSetup(dataStoreOptions)
plugin.setDeprecatedStringList(listKey, listOf(""), dataStoreOptions)
val result = plugin.getStringList(listKey, dataStoreOptions)
Assert.assertEquals(result?.jsonEncodedValue, null)
Assert.assertEquals(result?.type, StringListLookupResultType.PLATFORM_ENCODED)
}

@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?.type, StringListLookupResultType.UNEXPECTED_STRING)
}

@Test
Expand Down Expand Up @@ -217,7 +237,27 @@ 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)
Assert.assertEquals(result?.type, StringListLookupResultType.JSON_ENCODED)
}

@Test
fun testSetAndGetStringListWithSharedPreferencesRedirectsForPlatformEncoded() {
val plugin = pluginSetup(sharedPreferencesOptions)
plugin.setDeprecatedStringList(listKey, listOf(""), sharedPreferencesOptions)
val result = plugin.getStringList(listKey, sharedPreferencesOptions)
Assert.assertEquals(result?.jsonEncodedValue, null)
Assert.assertEquals(result?.type, StringListLookupResultType.PLATFORM_ENCODED)
}

@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?.type, StringListLookupResultType.UNEXPECTED_STRING)
}

@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 All @@ -18,6 +18,18 @@ PlatformException _createConnectionError(String channelName) {
);
}

/// Possible types found during a getStringList call.
enum StringListLookupResultType {
/// A deprecated platform-side encoding string list.
platformEncoded,

/// A JSON-encoded string list.
jsonEncoded,

/// A string that doesn't have the expected encoding prefix.
unexpectedString,
}

class SharedPreferencesPigeonOptions {
SharedPreferencesPigeonOptions({
this.fileName,
Expand All @@ -44,15 +56,49 @@ class SharedPreferencesPigeonOptions {
}
}

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

/// The JSON-encoded stored value, or null if something else was found.
String? jsonEncodedValue;

/// The type of value found.
StringListLookupResultType type;

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

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

class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
if (value is int) {
buffer.putUint8(4);
buffer.putInt64(value);
} else if (value is SharedPreferencesPigeonOptions) {
} else if (value is StringListLookupResultType) {
buffer.putUint8(129);
writeValue(buffer, value.index);
} else if (value is SharedPreferencesPigeonOptions) {
buffer.putUint8(130);
writeValue(buffer, value.encode());
} else if (value is StringListResult) {
buffer.putUint8(131);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
Expand All @@ -63,7 +109,12 @@ class _PigeonCodec extends StandardMessageCodec {
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 129:
final int? value = readValue(buffer) as int?;
return value == null ? null : StringListLookupResultType.values[value];
case 130:
return SharedPreferencesPigeonOptions.decode(readValue(buffer)!);
case 131:
return StringListResult.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
}
Expand Down Expand Up @@ -373,8 +424,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 +446,7 @@ class SharedPreferencesAsyncApi {
details: pigeonVar_replyList[2],
);
} else {
return (pigeonVar_replyList[0] as String?);
return (pigeonVar_replyList[0] as StringListResult?);
}
}

Expand Down
Loading
Loading