Home > front end >  Issue with sealed interface type safety inside when - Kotlin
Issue with sealed interface type safety inside when - Kotlin

Time:09-29

In order to handle Retrofit api calls I have a sealed interface as below:

sealed interface DataSourceResponseWrapper<out T> {
    data class Success<out T>(val result: T) : DataSourceResponseWrapper<T>
    data class Error(val throwable: Throwable) : DataSourceResponseWrapper<Nothing>
}

As you can see Success<out T> is a generic class but Error is not.

Here is ItemDataRepository:

class DataRepository constructor(private val itemApiDataSource: ItemApiDataSource) {

    suspend fun loadData(search: String, days: Int, aqi: Boolean, alerts: Boolean): DataSourceResponseWrapper<LocalItemResponse> =
        coroutineScope {
            withContext(Dispatchers.IO) {
                val response = try {
                    DataSourceResponseWrapper.Success(weatherApiDataSource.getWeatherInfo(search, days, if (aqi) "yes" else "no", if (alerts) "yes" else "no"))
                } catch (throwable: Throwable) {
                    DataSourceResponseWrapper.Error(throwable)
                }

                when (response) {
                    is DataSourceResponseWrapper.Success<ApiWeatherResponse> ->
                        DataSourceResponseWrapper.Success(WeatherConverter.convertToLocal(response.result))
                    is DataSourceResponseWrapper.Error -> response
                }
            }
        }

In DataRepository class I check if the result is successful or not. If it is, repository class converts the returned ApiItemResponse to a LocalItemResponse and returns the result. And if it's not, returns the response itself

The code works perfectly fine until I change when statements to the following:

                when (response) {
                    is DataSourceResponseWrapper.Success<ApiWeatherResponse> ->
                        DataSourceResponseWrapper.Success(WeatherConverter.convertToLocal(response.result))
                    else -> response
                }

It gives me an error saying:

Required:
DataSourceResponseWrapper<LocalItemResponse>
Found:
DataSourceResponseWrapper<Any>

So my question is Why Kotlin does not smartly cast the response as before? And the other question is How can I use else without needing to check type?

CodePudding user response:

Some cases are simply too sophisticated (too many steps of logic) for the compiler to sort out and infer the types for you. In this case, instead of using an else branch, you can be specific. This is the way you should be using a sealed type anyway (no else branch if it can be avoided):

when (response) {
    is DataSourceResponseWrapper.Success<ApiWeatherResponse> ->
                    DataSourceResponseWrapper.Success(WeatherConverter.convertToLocal(response.result))
    is DataSourceResponseWrapper.Error -> response
}

CodePudding user response:

You can in fact write

else -> response as DataSourceResponseWrapper<LocalItemResponse>

to make it work. Although the IDE is likely to warn for an unchecked cast. But it's just a warning. You can safely ignore it because you know for a fact that you can cast it there. It will compile and run just fine. But admittedly it is ugly to have the warning there.

  • Related