Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin sealed class subclass needs to be casted to base class if provided as RxJava Observable

I'm trying to avoid terminal states of RxJava chains in my app written in Kotlin, so I found out that it is right thing to transform Observable<T> to Observable<Result<T>> where Result is sealed class.

sealed class Result<T>
data class Success<T>(val data: T) : Result<T>()
data class Failure<T>(val throwable: Throwable) : Result<T>()

And let's say I have this network request observable.

fun getOrganization(): Observable<Result<Boolean>> {
    return api.getOrganization("google")
            .map { Success(true) }
            .onErrorReturn { Failure(RuntimeException("throwable")) }
}

So what I want to do in the end is to handle result like this.

fun main(args: Array<String>) {
    getOrganization().subscribe({
        when (it) {
            is Success -> print("success = ${it.data}")
            is Failure -> print("failure = ${it.throwable}")
        }
    })
}

Everything should work great, but I have this error in IDE.

error

Isn't it just simple using of subclass instead of base class?

I found out that if I explicitly cast Success(true) to Result<Boolean> everything works, but shows this warning.

warning

Why it even happens and what am I doing wrong here?

UPD. It looks like the main issue here is near rxjava's onErrorReturn operator. If I remove it completely, then it works even without out keyword like answers suggest.

without onErrorReturn

If I return Success from both map and onErrorReturn then out keyword helps to get rid of errors.

But if I return Failure from onErrorReturn there's still error. Compiler has to know that Failure Result typed with the same Boolean as Success. What should I do to avoid it or to satisfy it's requirements?

with failure

like image 440
pavelkorolevxyz Avatar asked Jun 15 '17 10:06

pavelkorolevxyz


People also ask

Can sealed class be instantiated in Kotlin?

Implementing Kotlin Sealed Classes To specify a sealed class, you need to add the modifier sealed . A sealed class cannot be instantiated.

What defines a sealed class in Kotlin?

Sealed classes are used for representing restricted class hierarchies, when a value can have one of the types from a limited set, but cannot have any other type.

Can you have sealed interface in Kotlin?

note. enum classes can't extend a sealed class (as well as any other class), but they can implement sealed interfaces.

What is sealed class in kotlin Mindorks?

Sealed classes give us the flexibility of having different types of subclasses and also containing the state.


2 Answers

maybe you misunderstand the generic variance in kotlin. it works fine since Success<Boolean> is a subtype of Result<Boolean>. so the "No need cast" warning reported and the code below works fine:

val ok:Success<Boolean>  = Success(true);
val result:Result<Boolean>  = ok;

But you can't assign a Success<Boolean> to a Result<Any> since their type parameters are different, so this why compiler reports the "Type mismatch" error , for example:

val ok:Success<Boolean>  = Success(true);
val result1:Result<Any>  = ok;// error
val result2:Result<out Any>  = ok;// ok

to fix the error and warnings you can try the code below:

fun getOrganization(): Observable<out Result<Boolean>> {
    return api.getOrganization("google")
            .map<Result<Boolean>> { Success(true) }
            .onErrorReturn { Failure(RuntimeException("throwable")) }
}

for more details, you can see java generic subtypes & kotlin type projections.

java generic subtype

like image 56
holi-java Avatar answered Nov 11 '22 16:11

holi-java


An Observable<Success<Boolean>> is not a subtype Observable<Result<Boolean>>, just as List<String> is not a subtype of List<Object>. See the Generics documentation.

To solve this, either return an Observable<out Result<Boolean>>, or explicitly add a type to your map function:

.map<Result<Boolean>> { Success(true) }
like image 20
nhaarman Avatar answered Nov 11 '22 18:11

nhaarman