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>.
Related
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.
I have a mobile application that makes HTTP requests and waits for responses standardized by JSON attributes. I need to throw an Exception globally when a certain attribute is false.
Example:
HTTP RESPONSE 200 OK
{
"data": [],
"message": "An error was happened!",
"success": false
}
In the example above, the HTTP response returned status code 200, but the 'success' attribute is signaling that an error has occurred.
I need to make Retrofit understand that this attribute threw an exception from attr 'message'.
Googling I managed to find some examples where I was successful, using the Custom Call Adapter for this purpose:
class RxErrorHandlingCallAdapterFactory: CallAdapter.Factory() {
companion object {
fun create() : CallAdapter.Factory = RxErrorHandlingCallAdapterFactory()
}
private val _original by lazy {
RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())
}
override fun get(
returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
val wrapped = _original.get(returnType, annotations, retrofit) as CallAdapter<out Any, *>
return RxCallAdapterWrapper(wrapped)
}
private class RxCallAdapterWrapper<R>(
val _wrappedCallAdapter: CallAdapter<R, *>
) : CallAdapter<R, Observable<R>> {
override fun responseType(): Type = _wrappedCallAdapter.responseType()
#SuppressLint("CheckResult")
override fun adapt(call: Call<R>): Observable<R> {
val adapted = (_wrappedCallAdapter.adapt(call) as Observable<R>)
if ((responseType() == DefaultResponseModel::class.java
|| (responseType() as Class<R>).superclass == DefaultResponseModel::class.java)
) {
(adapted as Observable<DefaultResponseModel>).blockingFirst()?.let {
if (it.success == false) {
throw Throwable(it.message)
}
}
}
return adapted
}
}
}
The problem in fact is that with each HTTP request my application literally crashes for a few long seconds, causing undesirable black screens.
Analyzing the code I managed to find the cause of the slowness, but I don't know how to solve the problem.
(adapted as Observable<DefaultResponseModel>).blockingFirst()
The code snippet above gets the Model of the 'DefaultResponseModel' class from the Stream of the Observable. But for some reason this causes an exaggerated slowdown in the App with each request.
I would be grateful if someone could help me with this endeavor :D
--
Libraries:
Reotrofit 2.6
Kodein 6.4.1
Rxjava2 2.1.1
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...")
}
}
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?
This scene takes place in an Android app using Retrofit2 and Moshi for JSON deserialization.
In a case where you don't have control over the server's implementation, and this said server have an inconsistent behavior in how it answers requests (also know as "a bad case"):
Is there a way to handle com.squareup.moshi.JsonDataException without crashing?
For example you expected a JSONArray, and here comes a JSONObject. Crash. Is there another way to handle this than having the app crashing?
Also in the case the server's implementation is updated, wouldn't it be better to display an error message to the user, instead of having it to crash / be totally out of service, even for one wrong request?
Make the call with Retrofit and use try and catch to handle exceptions, something similar to:
class NetworkCardDataSource(
private val networkApi: NetworkCardAPI,
private val mapper: CardResponseMapper,
private val networkExceptionMapper: RetrofitExceptionMapper,
private val parserExceptionMapper: MoshiExceptionMapper
) : RemoteCardDataSource {
override suspend fun getCard(id: String): Outcome<Card, Throwable> = withContext(Dispatchers.IO) {
val response: Response<CardResponseJson>
return#withContext try {
response = networkApi.getCard(id)
handleResponse(
response,
data = response.body(),
transform = { mapper.mapFromRemote(it.card) }
)
} catch (e: JsonDataException) {
// Moshi parsing error
Outcome.Failure(parserExceptionMapper.getException(e))
} catch (e: Exception) {
// Retrofit error
Outcome.Failure(networkExceptionMapper.getException(e))
}
}
private fun <Json, D, L> handleResponse(response: Response<Json>, data: D?, transform: (D) -> L): Outcome<L, Throwable> {
return if (response.isSuccessful) {
data?.let {
Outcome.Success(transform(it))
} ?: Outcome.Failure(RuntimeException("JSON cannot be deserialized"))
} else {
Outcome.Failure(
HTTPException(
response.message(),
Exception(response.raw().message),
response.code(),
response.body().toString()
)
)
}
}
}
where:
networkApi is your Retrofit object,
mapper is a class for mapping the received object to another one used in your app (if needed),
networkExceptionMapper and parserExceptionMapper map Retrofit and Moshi exceptions, respectively, to your own exceptions so that Retrofit and Moshi exceptions do not spread all over your app (if needed),
Outcome is just a iOS Result enum copy to return either a Success or a Failure result but not both,
HTTPException is a custom Runtime exception to return unsuccessful request.
This a snippet from a clean architecture example project.