Pass a sealed class as function argument android - android

I have a sealed class which represents the Retrofit Response of my API.
sealed class NetworkResponse<out T : Any, out U : Any> {
data class Success<T : Any>(val body: T) : NetworkResponse<T, Nothing>()
data class ApiError<U : Any>(val body: U, val code: Int) : NetworkResponse<Nothing, U>()
data class NetworkError(val error: IOException) : NetworkResponse<Nothing, Nothing>()
data class UnknownError(val error: Throwable?) : NetworkResponse<Nothing, Nothing>()
}
So now i want to create a function that handles all the errors of a failed request. I want to have only one argument that represents either ApiError or NetworkError or UnknownError
fun networkErrorHanlder(mError: <what_should_i_put_here??>) {
// check if error is Api or Network or Unknown and do stuff...
}
What should be the type of the argument?

Since you care only about ApiError, NetworkError and UnknownError, which all derive from NetworkResponse but don't use the first generic type, you can specify that you don't care about it using * (Actually, depending on what you want to do with mError, you can replace U with * too - that is the case in the code below, but I introduced U just in case). In that case, you should accept a NetworkReponse:
fun <U : Any> networkErrorHanlder(mError: NetworkResponse<*, U>) {
when(mError) {
is NetworkResponse.ApiError ->
print("Api stuff: ${mError.body}")
is NetworkResponse.NetworkError ->
print ("Network stuff: ${mError.error}")
is NetworkResponse.UnknownError ->
print("Unknown: ${mError.error}")
else -> print("It must've been a Success...")
}
}

Related

Issue with sealed interface type safety inside when - Kotlin

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?
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
}
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.

Map Retrofit RxJava Result into sealed class Success and failure

I am using retrofit with RXJava to call my APIs, i am trying to map my call result into sealed class success and failure parts,
I did the mapping part but I am always getting this error java.lang.RuntimeException: Failed to invoke private Result() with no args
Here is my code:
Retrofit interface
#POST("$/Test")
fun UpdateProfile(#Body testBody: TestBody): Single<Result<TestResult>>
Result Sealed Class
sealed class Result<T> {
data class Success<T>(val value: T) : Result<T>()
data class Failure<T>(val throwable: Throwable) : Result<T>()}
Call and Mapping
webServices.UpdateProfile(testBody)
.onErrorReturn {
Failure(it)
}
.map {
when (it) {
is Result.Failure -> Failure(it.throwable)
is Success -> Success(it.value)
}
}.subscribe()
anyone can help with that? why I am getting this error?
The problem is your retrofit interface return type: Single<Result<TestResult>>. Retrofit has no way of knowing that your Result is a polymorphic type, it'll just try to instantiate whichever class you specify. In this case Result can't be instantiated directly because it is sealed.
You need to either use a non-polymorphic return type, or configure retrofit accordingly.

Android kotlin type inheritance failed

INTRODUCTION:
I created an android project following this example: https://github.com/android/architecture-samples/
I had to add a class that holds the response status(Success/Error) and it's value, in the repository it looks basically like this:
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
It is meant to be better than classic:
class Result<T> (
val success: Boolean,
val data: T?,
val exception: Exception?
)
Because :
- In this case the Success result definitely has only data and the Error only exception.
- As both Success and Error Message are inheriting from Result, due to Kotlin Smart Casts, the validation looks simpler:
var responce: Result<DataEntity> = dataSource.GetData()
if (responce is Success) {
doSomethingWith(responce.data)
} else if (responce is Error) {
throw responce.exception
}
PROBLEM:
All good, but when i'm trying to asynchronously observe data from a local dataSource (using Room lib):
interface TaskDao {
#Query("SELECT * FROM tasks")
fun observeTasks(): LiveData<List<TaskEntity>>
}
class SqlLocalDataSource(
private val taskDao: TaskDao,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
): LocalDataSource {
override suspend fun observeTasks(): LiveData<Result<List<TaskEntity>>> = withContext(ioDispatcher) {
taskDao.observeTasks().map {
Success(it)
}
}
}
It gives me the following Error: Type inference failed. Expected type mismatch: inferred type is LiveData<Result.Success<List<TaskEntity>>> but LiveData<Result<List<TaskEntity>>> was expected
Which is strange because Success inherits from Result
I TRYED:
I make sure that the types are correct (Result ids not from some other library)
To make the function run on the main thread, but its impossible to call DB from the main thread
It gives me the following Error: Type inference failed. Expected type mismatch: inferred type is LiveData<Result.Success<List<TaskEntity>>> but LiveData<Result<List<TaskEntity>>> was expected
Which is strange because Success inherits from Result
But LiveData<Success<...>> does not inherit from LiveData<Result<...>>. Please read about variance and take into account that LiveData is declared in Java and so can't be covariant.
I don't know why type inference would fail with suspend and work without it, but the problem can be fixed by being more explicit about types:
taskDao.observeTasks().map {
Success(it) as Result<List<TaskEntity>>
}
Or better, to avoid a cast:
fun <T> success(x: T): Result<T> = Success(x)
taskDao.observeTasks().map {
success(it)
}
The Problem was that its not permitted to Use LiveData in a suspend function, after I changed the function to a non suspended the error disappeared :
override fun observeTasks(): LiveData<Result<List<TaskEntity>>> {
return taskDao.observeTasks().map {
Success(it)
}
}
May be somebody can explain why is it?

Kotlin generic parameter using in function

i got solution of this question: Wildcards generic in Kotlin for parameter, but now i have other question which still related to kotlin generic
I have an abstract class using for listen api callback like below. ApiRs is a parent object that every API response object inherit from it
abstract class ApiCallback<in T : ApiRs> {
open fun onSucceed(apiRsModel: T) {}
open fun onFailed(code: Int,
message: String) {
}
}
this time i write a function to handle api succeed with Retrofit2, than check something and callback to UI, here is my function:
fun <T : ApiRs> callbackWithSucceed(apiCallback: ApiCallback<T>?,
context: Context,
response: Response<out ApiRs>?) {
// unexpected error
if (response == null) {
encounterUnexpectedError(apiCallback, context, null)
return
}
// check http
val httpSucceed = response.code() == CODE_HTTP_SUCCEED
&& response.isSuccessful
&& response.body() != null
if (!httpSucceed) {
// HTTP response with error
callbackWithFailed(
apiCallback,
response.code(),
response.message())
return
}
apiCallback?.onSucceed(response.body()!!)
}
}
response is Retrofit2 class, which contains my API response model (body) and every response model are inherit ApiRs, my goal is pass the model to the abstract class using this way apiCallback?.onSucceed(response.body()!!) but it will show an error
Type mismatch, required T? but Found ApiRs?
Sorry about i have bad concept of generic. I think function open fun onSucceed(apiRsModel: T) {} T should inherit ApiRs because i defined in class, so i cannot understand why error message shown?
Your response: Response<out ApiRs>? has to be response: Response<out T>?, then the error should be gone.
The type of T which will be passed to the method onSuccess must match the generic type of the apiCallback parameter, which isn't <ApiRs> but <T>.

Kotlin type mismatch compile error: Require Success<T>, Found MyError

I am encountering an issue that the following code can't be compiled in kotlin.
// StateModel.kt
sealed class StateModel
class Loading : StateModel()
data class Success<T: Any>(val data: T) : StateModel()
data class MyError(val message: String) : StateModel()
// StateModelTransformer.kt
class StateModelTransformer<T: Any> : FlowableTransformer<T, StateModel> {
override fun apply(upstream: Flowable<T>): Publisher<StateModel> {
return upstream
.map { data -> Success(data) }
.onErrorReturn { error ->
MyError(error.message) // compile error, Type mismatch, Require Success<T>, Found MyError
}
.startWith(Loading()) // compile error, none of the following function can be called with the arguments supplied
}
}
I have no idea why the onErrorReturn says requiring a Success<T> type but a StateModel type.
Thanks
Here are the relevant declarations in Flowable, for reference. Let's ignore onErrorReturn; it's not relevant to the problem here.
public Flowable<T> {
public <R> Flowable<R> map(Function<? super T, ? extends R> mapper);
public Flowable<T> startWith(T value);
}
These are the types Kotlin infers.
val upstream: Flowable<T>
val mapper: (T) -> Success<T> = { data -> Success(data) }
val map: ((T) -> Success<T>) -> Flowable<Success<T>>
= upstream::map
val mapped: Flowable<Success<T>> = map(mapper)
val loading: Loading = Loading()
val startWith:
(Success<T>) -> Flowable<Success<T>>
= mapped::startWith
startWith(loading) // type mismatch
The more specific Success<T> type was already inferred earlier, and Kotlin does not backtrack to find the more general StateModel type. To force that to happen, you can manually state the types, for example
// be explicit about the general type of the mapper
upstream.map { data -> Success(data) as StateModel }.startWith(Loading())
// be explicit about the generic type R = StateModel
upstream.map<StateModel> { data -> Success(data) }.startWith(Loading())
Incidentally, you're currently losing <T> in StateModel. I would suggest changing the base class to include the type parameter.
sealed class StateModel<out T: Any>
object Loading : StateModel<Nothing>()
data class Success<T: Any>(val data: T) : StateModel<T>()
data class MyError(val message: String) : StateModel<Nothing>()
This will let you write, for example,
val <T: Any> StateModel<T>.data: T?
get() = when (this) {
is Success -> data
else -> null
}

Categories

Resources