Map Retrofit RxJava Result into sealed class Success and failure - android

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.

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.

Pass a sealed class as function argument 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...")
}
}

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?

Observable subscribe not getting called on same value

I have a BehaviourSubject as a callback for my Retrofit method.
private val loadCompleted = BehaviourSubject.create<List<String>>()
Inside my retrofit OnResponse/onFailure, I call
loadCompleted.onNext(myList) //inside retrofit onResponse and
loadCompleted.onError("Error") // inside retrofit onFailure
I am subscribed to a function which returns loadCompleted.
fun loadingCompleted() : Observable<List<String>>{return loadCompleted}
and then I subscribe to the loadingCompleted as
loadingCompleted.subscribe{list ->
//only called once
anotherfun(list)
}
The first time my Retrofit function gets called, I am able to get my subscribe called but subsequent calls to the same function doesn't fire the subscribe. I am assuming the calls normally return the same value because it is just a refresh and the data might not have changed. However, I still need to get the subscribe called so I can react accordingly. I have tried both BS and ReplaySubject but the result is the same. How do I use an observable to ensure I always get the subscribe called whenever I call onNext(x)/onComplete(x) even though x might not have changed?
You are probably finishing your BehaviorSubject stream with onComplete/onError. If you don't want to do that, wrap x/error to some kind of Result which is sealed class and has Success and Failure subclasses. Then always use subject.onNext() to emit.
sealed class Result<T> {
class Success<T>(val t: T) : Result<T>()
class Failure<T>(val e: Throwable) : Result<T>()
}
class Test {
val subject: BehaviorSubject<Result<List<String>>> = BehaviorSubject.create()
fun call() {
subject.onNext(Result.Success(listOf("example")))
subject.onNext(Result.Failure(RuntimeException("error")))
subject.subscribe{result->
when(result){
is Result.Success -> print(result.t)
is Result.Failure -> print(result.e.message)
}
}
}
}

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

Categories

Resources