The Code A works well in my Old version, now I update my Android Studio to 3.4.2, and buildToolsVersion "29.0.1", and I use the latest androidx.
But I get the error Type mismatch, you can see Image 1, how can I fix it? Thanks!
Image 1
Code A
class PreferenceTool<T>(private val context: Context, private val name: String, private val default: T) {
private val prefs: SharedPreferences by lazy {
context.defaultSharedPreferences
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = findPreference(name, default)
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
putPreference(name, value)
}
@Suppress("UNCHECKED_CAST")
private fun findPreference(name: String, default: T): T = with(prefs) {
val res: Any = when (default) {
is Long -> getLong(name, default)
is String -> getString(name, default)
is Int -> getInt(name, default)
is Boolean -> getBoolean(name, default)
is Float -> getFloat(name, default)
else -> throw IllegalArgumentException("This type can be saved into Preferences")
}
res as T
}
@SuppressLint("CommitPrefEdits")
private fun putPreference(name: String, value: T) = with(prefs.edit()) {
when (value) {
is Long -> putLong(name, value)
is String -> putString(name, value)
is Int -> putInt(name, value)
is Boolean -> putBoolean(name, value)
is Float -> putFloat(name, value)
else -> throw IllegalArgumentException("This type can't be saved into Preferences")
}.apply()
}
}
Added Content 1
And more, I find both the Code B and Code Ccan be compiled correctly. I don't know why. It seems that is String -> getString(name, default)
cause error.
Code B
@Suppress("UNCHECKED_CAST")
private fun findPreference(name: String, default: T): T = with(prefs) {
val res: Any = when (default) {
is Long -> getLong(name, default)
else -> throw IllegalArgumentException("This type can be saved into Preferences")
}
res as T
}
Code C
@Suppress("UNCHECKED_CAST")
private fun findPreference(name: String, default: T): T = with(prefs) {
val res: Any = when (default) {
is Long -> getLong(name, default)
is Int -> getInt(name, default)
is Boolean -> getBoolean(name, default)
is Float -> getFloat(name, default)
else -> throw IllegalArgumentException("This type can be saved into Preferences")
}
res as T
}
Added Content 2
It seems that prefs.getString(name, default)
return String?
by Image 2. I don't know if there is a bugs of Build 29.0.1?
Image 2
TypeMismatch is thrown by dynamic any accessor methods when type of the actual contents do not match what is trying to be accessed.
This code cannot be compiled, because SharedPreferences.getString()
returns String?
, which is not a subtype of Any
. Why did it work before, was it a bug?
This was not a bug, but a gradual movement to null-safety in android sdk. When kotlin became an official language for android development, they started to work on making android sdk kotlin-friendly. In particular, they were adding @Nullable
and @NonNull
annotations to sdk code. But doing so immediately would break compilation of lots of projects, so they decided to give developers some time to fix their code:
Normally, nullability contract violations in Kotlin result in compilation errors. But to ensure the newly annotated APIs are compatible with your existing code, we are using an internal mechanism provided by the Kotlin compiler team to mark the APIs as recently annotated. Recently annotated APIs will result only in warnings instead of errors from the Kotlin compiler. You will need to use Kotlin 1.2.60 or later.
Our plan is to have newly added nullability annotations produce warnings only, and increase the severity level to errors starting in the following year's Android SDK. The goal is to provide you with sufficient time to update your code.
As they said, they would increase severity level in following SDKs, and that is exactly what happened here. But the source code of SharedPreferences
has not been changed, so how did it happen? And the answer is @RecentlyNullable
.
(1) There are some annotations here which are not in the support library, such as @RecentlyNullable and @RecentlyNonNull. These are used only in the stubs to automatically mark code as recently annotated with null/non-null. We do not want these annotations in the source code; the recent-ness is computed at build time and injected into the stubs in place of the normal null annotations.
Even though SharedPreferences.getString
was annotated with @Nullable
long time ago, apparently their automated recent-ness computation decided to mark it as @RecentlyNullable
in android P. To prove that let's take a look at decompiled code.
Android P:
@RecentlyNullable
String getString(String var1, @RecentlyNullable String var2);
Android Q:
@Nullable
String getString(String var1, @Nullable String var2);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With