private fun updatePreference(preference: Preference?) {
val stringValue: CharSequence
when (preference) {
is SwitchPreferenceCompat, is SwitchPreference -> {
stringValue = preference.switchTextOn
}
}
...
}
Both SwitchPreference and SwitchPreferenceCompat have a switchTextOn property, yet that gives the error “Unresolved reference: switchTextOn”
However, the following does not have said issue:
private fun updatePreference(preference: Preference?) {
val stringValue: CharSequence
when (preference) {
is SwitchPreferenceCompat -> {
stringValue = preference.switchTextOn
}
is SwitchPreference -> {
stringValue = preference.switchTextOn
}
}
...
}
What appears to happen is that it falls back to Preference? in the former example and does a smart cast in the latter.
I believe it makes sense to a person familiar with the above classes that preference.switchTextOn in the code in the former example will resolve to a defined value.
I’m not sure as to the scope of implementation for something like this, but this would certainly be very handy in many other situations. Even if the functionality was such that it was only able to smart cast to TwoStatePreference, which is extended by both the above classes, that would still be incredibly useful.
Because switchTextOn is only available in a SwitchPreference / SwitchPreferenceCompat. I used this as a contrived example for that very reason.
Something such as isChecked would be available in TwoStatePreference, however.
when (preference) {
is TwoStatePreference -> {
// this is fine
stringValue = preference.isChecked
// However, this will fail
stringValue = preference.switchTextOn
}
}
If all we wanted was isChecked then that would work, however what if there was another class that extended TwoStatePreference and we didn’t want that to be included?
Here’s a better example:
open class A
open class B: A() {val arbitraryValue: Any? = null}
class C: B() {...}
class D: B() {...}
class E: B() {...}
class F {
fun check(a: A) {
when (a) {
is C, is D -> {
// This will fail
a.arbitraryValue
}
is E -> {
// This is fine
a.arbitraryValue
}
}
}
}
when you write when (preference) { is SwitchPreferenceCompat, is SwitchPreference -> {...your condition will be match if preference is a SwitchPreferenceCompat or a SwitchPreference. then the compiler cannot smartcast to any of these types because any of these type are candidate. So there is no smartcast here.
with comment in your code :
private fun updatePreference(preference: Preference?) {
val stringValue: CharSequence
when (preference) {
is SwitchPreferenceCompat, is SwitchPreference -> {
/*
here compiler does not know if its a SwitchPreferenceCompat or a SwitchPreference.
kotlin will not assume anything and stick to the original type (Preference?).
there is no smartcast
*/
stringValue = preference.switchTextOn //error
}
}
...
}
private fun updatePreference(preference: Preference?) {
val stringValue: CharSequence
when (preference) {
is SwitchPreferenceCompat -> {
/*
here the compiler knows preference is not only a Preference?,
but also a SwitchPreferenceCompat
kotlin can then consider that preference is a SwitchPreferenceCompat:
there is a "smartcast" in this case
so now kotlin knows preference is a SwitchPreferenceCompat, it can access switchTextOn property
*/
stringValue = preference.switchTextOn
}
is SwitchPreference -> {
// same case here, with SwitchPreference instead of SwitchPreferenceCompat
stringValue = preference.switchTextOn
}
}
...
}
if SwitchPreferenceCompat and SwitchPreference are part of your code, you can add add an interface on them and use it to access switchTextOn:
interface WithSwitchTextOn{
val switchOnText: CharSequence
}
class SwitchPreferenceCompat ... : WithSwitchTextOn {
...
override val switchOnText: CharSequence
...
}
class SwitchPreference ... : WithSwitchTextOn {
...
override val switchOnText: CharSequence
...
}
then you can write :
private fun updatePreference(preference: Preference?) {
var stringValue: CharSequence
when (preference) {
is WithSwitchOnText -> {
stringValue = preference.switchTextOn
}
}
...
}
if you don’t have control on these 2 classes… you will have to treat them separatly (like in your second example).
btw, you stringValue is not initialized, I guess this is just for the example so its ok
For the most part I was aware of the compiler’s decision rationale. I was attempting to make a suggestion, but apparently that didn’t come across fully.
That said, I like your suggestion for using an interface.
So I’ll see about using that when I have the opportunity to do so.
Yeah yeah, haha
I was trying to minimize what I used in the example for legibility.
While it’s great that you like the solution of using an interface, what you actually want (and simulate this way) are union types. I hope I did not mean intersection types. I always get them mixed up. I know there were a few discussions about them previously. While I am personally not a fan of declaring union types explicitly I think they might have some value in combination with smart casts (is-expressions).
Not sure if there has been any real discussion about this.