Custom Error Handler for retrofit failure cases - android

I'm new to retrofit, and attempting to set a converter that will automatically deserialize error messages for result.errorBody
CustomErrorHandler
class CustomErrorHandler(private val gson: Gson) : Converter<ResponseBody, Error> {
#Throws(IOException::class)
override fun convert(responseBody: ResponseBody): Error {
val error = gson.fromJson(responseBody.charStream(), Error::class.java)
responseBody.close()
throw Exception(error.message)
}
class Factory(private val gson: Gson) : Converter.Factory() {
override fun responseBodyConverter(type: Type, annotations: Array<Annotation>, retrofit: Retrofit): Converter<ResponseBody, *>? {
val typeToken = TypeToken.get(type)
if (typeToken.rawType != Error::class.java) {
return null
}
return CustomErrorHandler(gson)
}
}
}
Retrofit
val retrofit = Retrofit.Builder().baseUrl("http://192.168.0.1:8080")
.addConverterFactory(CustomErrorHandler.Factory(GsonBuilder().create()))
.addConverterFactory(GsonConverterFactory.create()).build()
Error
data class Error(val code: String, val message: String) {
override fun toString(): String {
return "code: $code, message: $message"
}
}
RoleService
interface RoleService {
#GET("/roles")
#Headers("Accept: application/json")
suspend fun findAll(): Response<List<Role>>
}
Attempt #1
expecting that following throw statement will have result.errorBody as the Error class or please advise on how it's supposed to be done.
suspend fun findAll(): List<Role>? {
val result = roleService.findAll()
if (result.isSuccessful)
return result.body()
throw Exception(result.errorBody().toString()) // result.errorBody to be Error class???
}
Attempt #2
A simple approach without using any converter and deserializing errorBody to Error class
suspend fun findAll(): List<Role>? {
val result = roleService.findAll()
if (result.isSuccessful)
return result.body()
val e = Gson().fromJson(result.errorBody().toString(), Error::class.java) // fails too
throw Exception(e.message)
}

This inline solution helped, I couldn't get Converter working.
throw Exception(gson.fromJson(result.errorBody()?.string(), Error::class.java)?.message)
complete code
suspend fun findAll(): List<Role>? {
val result = roleService.findAll()
if (result.isSuccessful)
return result.body()
throw Exception(gson.fromJson(result.errorBody()?.string(), Error::class.java)?.message)
}

Related

Android Retrofit2: Serialize null without GSON or JSONObject

The result of API call in my Android app can be a JSON with configuration which is mapped to SupportConfigurationJson class, or just pure null. When I get a JSON, the app works properly, but when I get null, I get this exception:
kotlinx.serialization.json.internal.JsonDecodingException: Expected start of the object '{', but had 'EOF' instead
JSON input: null
I should avoid using GSON in this project. I also found a solution, where API interface will return Response<JSONObject>, and after that my repository should check if this JSONObject is null and map it to SupportConfigurationJson if not. But in the project we always used responses with custom classes so I wonder, is there any other solution to get response with null or custom data class?
GettSupportConfiguration usecase class:
class GetSupportConfiguration #Inject constructor(
private val supportConfigurationRepository: SupportConfigurationRepository
) {
suspend operator fun invoke(): Result<SupportConfiguration?> {
return try {
success(supportConfigurationRepository.getSupportConfiguration())
} catch (e: Exception) {
/*
THIS SOLUTION WORKED, BUT I DON'T THINK IT IS THE BEST WAY TO SOLVE THE PROBLEM
if (e.message?.contains("JSON input: null") == true) {
success(null)
} else {
failure(e)
}
*/
//I WAS USING THROW HERE TO SEE WHY THE APP ISN'T WORKING PROPERLY
//throw(e)
failure(e)
}
}
}
SupportConfigurationJson class:
#Serializable
data class SupportConfigurationJson(
#SerialName("image_url")
val imageUrl: String,
#SerialName("description")
val description: String,
#SerialName("phone_number")
val phoneNumber: String?,
#SerialName("email")
val email: String?
)
SupportConfigurationRepository class:
#Singleton
class SupportConfigurationRepository #Inject constructor(
private val api: SupportConfigurationApi,
private val jsonMapper: SupportConfigurationJsonMapper
) {
suspend fun getSupportConfiguration(): SupportConfiguration? =
mapJsonToSupportConfiguration(api.getSupportConfiguration().extractOrThrow())
private suspend fun mapJsonToSupportConfiguration(
supportConfiguration: SupportConfigurationJson?
) = withContext(Dispatchers.Default) {
jsonMapper.mapToSupportSettings(supportConfiguration)
}
}
fun <T> Response<T?>.extractOrThrow(): T? {
val body = body()
return if (isSuccessful) body else throw error()
}
fun <T> Response<T>.error(): Throwable {
val statusCode = HttpStatusCode.from(code())
val errorBody = errorBody()?.string()
val cause = RuntimeException(errorBody ?: "Unknown error.")
return when {
statusCode.isClientError -> ClientError(statusCode, errorBody, cause)
statusCode.isServerError -> ServerError(statusCode, errorBody, cause)
else -> ResponseError(statusCode, errorBody, cause)
}
}
SupportConfigurationApi class:
interface SupportConfigurationApi {
#GET("/mobile_api/v1/support/configuration")
suspend fun getSupportConfiguration(): Response<SupportConfigurationJson?>
}
SupportConfigurationJsonMapper class:
class SupportConfigurationJsonMapper #Inject constructor() {
fun mapToSupportSettings(json: SupportConfigurationJson?): SupportConfiguration? {
return if (json != null) {
SupportConfiguration(
email = json.email,
phoneNumber = json.phoneNumber,
description = json.description,
imageUrl = Uri.parse(json.imageUrl)
)
} else null
}
}
I create Retrofit like this:
#Provides
#AuthorizedRetrofit
fun provideAuthorizedRetrofit(
#AuthorizedClient client: OkHttpClient,
#BaseUrl baseUrl: String,
converterFactory: Converter.Factory
): Retrofit {
return Retrofit.Builder()
.client(client)
.baseUrl(baseUrl)
.addConverterFactory(converterFactory)
.build()
}
#Provides
#ExperimentalSerializationApi
fun provideConverterFactory(json: Json): Converter.Factory {
val mediaType = "application/json".toMediaType()
return json.asConverterFactory(mediaType)
}
Everything is explained here (1min read)
Api is supposed to return "{}" for null, If you can't change API add this converter to Retrofit
You are interacting with your repository directly, i will suggest to use
usecases
to interact with data layer.
Because you are not catching this exception over here, your app is crashing
suspend fun getSupportConfiguration(): SupportConfiguration? =
mapJsonToSupportConfiguration(api.getSupportConfiguration().extractOrThrow())
Usecase usually catch these errors and show useful error msg at the ui.

How to parse response with retrofit2 Kotlin

I'm stuck with parsing the response. In Swift I can make a codable to help parsing the json response. I'm new to Kotlin and I'm working on someone else existing project. I made a data class for string and boolean but I don't know the syntax to parse it. Please help and thank you.
The responseBody json
{
"bearerToken": "########",
"staySignIn": false
}
//Interface
interface PostInterface {
class User(
val email: String,
val password: String
)
#POST("signIn")
fun signIn(#Body user: User): Call<ResponseBody>
//Network handler
fun signIn(email: String, password: String): MutableLiveData<Resource> {
val status: MutableLiveData<Resource> = MutableLiveData()
status.value = Resource.loading(null)
val retrofit = ServiceBuilder.buildService(PostInterface::class.java)
retrofit.signIn(PostInterface.User(email, password)).enqueue(object : Callback<ResponseBody> {
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
errorMessage(status)
}
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
if (response.code() == 200) {
try {
status.value = //how to parse using the model??
} catch (ex: Exception) {
parseError(400, response.body().toString(), status)
}
} else {
//do something...
}
}
})
return status
}
//Model
data class SignInModel(
#field:SerializedName("bearerToken")
val bearerToken: String? = null,
#field:SerializedName("staySignIn")
val staySignIn: Boolean? = null
)
//Storing value class
class RrefManager constructor(var applicationContext: Context) {
private fun getSharedPrefEditor(): sharedPrefEditor.Editor {
return applicationContext.getSharedPrefEditor(prefStorageName, Context.MODE_PRIVATE).edit()
}
public fun setBearerToken(token: String) {
getSharedPrefEditor().putString("bearerToken", token).apply()
}
public fun setStaySignIn(enabled: Boolean) {
getSharedPrefEditor().putBoolean("staySignIn", enabled).apply()
}
}
//SignIn Button
viewModel.signIn().observe(viewLifecycleOwner, androidx.lifecycle.Observer { v ->
if (v.status == Resource.Status.SUCCESS) {
val model = v.data as SignInModel
pref.setToken(model.token as String) //storing value
pref.setTwoFactorEnabled(model.twoFactorEnabled as Boolean) //storing value
} else if (v.status == Resource.Status.ERROR) {
//do something...
}
})
I think your best option to achieve something like the codable in swift is to use Gson library for parsing api responses.
When you create the retrofit instance you pass the gson converter to the builder like:
val retrofit = Retrofit.Builder()
.baseUrl(BaseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
After you have done that you can make the api return the response you have as the data class, like:
//Interface
interface PostInterface {
#POST("signIn")
fun signIn(#Body user: User): Call<SignInModel>
}
To read the answer from the callback on your class, the response inside the network call is already parsed into your model in the callback. All the retrofit callback should be changed to receive Callback and then you can access directly like status.value = response.body()
For more info you can consult the retrofit library page where it gives all the details and explanations on how to use it correctly.
https://square.github.io/retrofit/

Android Retrofit cannot parse RequestBody

I have trouble with a Retrofit call. I have a node js server which is working fine and I want to send a login request to it from Android.
The HTTP request expects a JSON request body. I try to create it with OkHttp3.
Here is my interface code:
interface ApiService {
#POST("/login-player")
suspend fun loginPlayer(#Body body: RequestBody): Call<PlayerApiModel>
}
Of course, I have more paths than this but first of all, I want this to work.
My model class, if anyone interested:
data class PlayerApiModel(
#SerializedName("player_id") #Expose var playerId: Int,
#SerializedName("_id") #Expose var id: String,
#SerializedName("player_name") #Expose var name: String,
#SerializedName("password_hash") #Expose var password: String,
#SerializedName("_v") #Expose var version: Int
)
This is exactly the same schema as I get from the node server. But the problem is the request cannot even reach the server.
My Retrofit singleton looks like this:
class RetrofitInstance {
companion object {
private const val URL = "https://pedro.sch.bme.hu/"
val retrofit: ApiService by lazy {
val interceptor = HttpLoggingInterceptor()
val httpClient = OkHttpClient.Builder()
.addInterceptor(interceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.followRedirects(true)
.followSslRedirects(true)
.addInterceptor { chain ->
val newRequest = chain.request().newBuilder()
.addHeader("Authorization", UUID.randomUUID().toString())
.build()
chain.proceed(newRequest)
}
val builder = Retrofit.Builder()
.baseUrl(URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addCallAdapterFactory(SimpleCallAdapterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(MoshiConverterFactory.create())
val retrofit = builder
.client(httpClient.build())
.build()
retrofit.create(ApiService::class.java)
}
}
}
And lastly here is the problematic code part: the call.
val playerName = bind.root.txtPlayerName.text.toString()
val password = bind.root.txtPassword.text.toString()
val jsonObject = JSONObject()
jsonObject.put("player_name", playerName)
jsonObject.put("password", password)
val jsonBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonObject.toString())
RetrofitInstance.retrofit.loginPlayer(jsonBody)
.enqueue(object : Callback<PlayerApiModel> {
override fun onResponse(call: Call<PlayerApiModel>, response: Response<PlayerApiModel>) {
println("${response.code()}: ${response.message()}")
when (response.code()) {
200 -> {
listener.goToMenu(playerName)
}
400 -> {
enableEditTexts()
Snackbar.make(bind.root, response.message(), Snackbar.LENGTH_LONG).show()
}
500 -> {
enableEditTexts()
serverErrorSnackbar()
}
}
}
override fun onFailure(call: Call<PlayerApiModel>, t: Throwable) {
Log.i("LoginViewModel::login()", t.message)
enableEditTexts()
}
})
The exception that I get comes to the loginPlayer() call.
This is the complete stack trace:
E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1
Process: neptun.jxy1vz.cluedo, PID: 14287
java.lang.IllegalArgumentException: Unable to create call adapter for class java.lang.Object
for method ApiService.loginPlayer
at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:755)
at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:240)
at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:165)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:170)
at retrofit2.Retrofit$1.invoke(Retrofit.java:147)
at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
at $Proxy1.loginPlayer(Unknown Source)
at neptun.jxy1vz.cluedo.ui.activity.login.LoginViewModel$login$1.invokeSuspend(LoginViewModel.kt:57)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Caused by: java.lang.IllegalArgumentException: Could not locate call adapter for class java.lang.Object.
Tried:
* retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
* neptun.jxy1vz.cluedo.network.call_adapter.SimpleCallAdapterFactory
* retrofit2.ExecutorCallAdapterFactory
at retrofit2.Retrofit.nextCallAdapter(Retrofit.java:241)
at retrofit2.Retrofit.callAdapter(Retrofit.java:205)
at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:238)
... 12 more
I/Process: Sending signal. PID: 14287 SIG: 9
Update:
I got a suggestion to remove RxJava2CallAdapterFactory from Retrofit.
Now the bottom of the stack trace is this:
Tried:
* neptun.jxy1vz.cluedo.network.call_adapter.SimpleCallAdapterFactory
* retrofit2.ExecutorCallAdapterFactory
at retrofit2.Retrofit.nextCallAdapter(Retrofit.java:241)
at retrofit2.Retrofit.callAdapter(Retrofit.java:205)
at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:238)
... 12 more
You can see here my SimpleCallAdapter, which is pretty simple (haha), I wrote this according to a tutorial, but I did not do any special thing in it:
class SimpleCallAdapterFactory private constructor() : CallAdapter.Factory() {
override fun get(returnType: Type?, annotations: Array<out Annotation>?, retrofit: Retrofit?): CallAdapter<*, *>? =
returnType?.let {
return try {
// get enclosing type
val enclosingType = (it as ParameterizedType)
// ensure enclosing type is 'Simple'
if (enclosingType.rawType != Simple::class.java)
null
else {
val type = enclosingType.actualTypeArguments[0]
SimpleCallAdapter<Any>(type)
}
} catch (ex: ClassCastException) {
null
} }
companion object {
#JvmStatic
fun create() = SimpleCallAdapterFactory()
}
}
class SimpleCallAdapter<R>(private val responseType: Type): CallAdapter<R, Any> {
override fun responseType(): Type = responseType
override fun adapt(call: Call<R>): Any = Simple(call)
}
class Simple<R>(private val call: Call<R>) {
fun run(responseHandler: (R?, Throwable?) -> Unit) {
// run in the same thread
try {
// call and handle response
val response = call.execute()
handleResponse(response, responseHandler)
} catch (t: IOException) {
responseHandler(null, t)
}
}
fun process(responseHandler: (R?, Throwable?) -> Unit) {
// define callback
val callback = object : Callback<R> {
override fun onFailure(call: Call<R>?, t: Throwable?) =
responseHandler(null, t)
override fun onResponse(call: Call<R>?, r: Response<R>?) =
handleResponse(r, responseHandler)
}
// enqueue network call
call.enqueue(callback)
}
private fun handleResponse(response: Response<R>?, handler: (R?, Throwable?) -> Unit) {
response?.let {
if (response.isSuccessful) {
handler(response.body(), null)
println(response.body())
}
else {
println("${response.code()}: ${response.message()}")
if (response.code() in 400..511)
handler(null, HttpException(response))
else
handler(response.body(), null)
}
}
}
}
And the usage of this:
RetrofitInstance.retrofit.loginPlayer(jsonBody).process { playerApiModel, throwable ->
println("Debug: ${playerApiModel?.name}")
}
And in the interface:
interface ApiService {
#POST("/login-player")
suspend fun loginPlayer(#Body body: RequestBody): Simple<PlayerApiModel>
}
End of update
Curious that it does not recognize the RequestBody type and instead it tries to pass java.lang.Object.
I have all the necessary dependencies in Gradle.

Retrofit & Moshi: Get request with sealed class & generics - Is it possible?

I have a sealed class for state handling of my Retrofit responses. It's members take a generic type. I would like to get Retrofit to be able to return the proper object, but I am stuck at this error: Unable to create converter for com.my.app.DataResult<?> - Cannot serialize abstract class com.my.app.DataResult
This is my DataResult class:
sealed class DataResult<out T> {
data class Success<out T>(val data: T?) : DataResult<T>()
data class Error<out T>(val code: Int? = null, val error: Exception? = null) : DataResult<T>()
object NetworkError : DataResult<Nothing>()
fun isSuccess() = this is Success<*>
fun isError() = this is Error<*>
fun data() = if (isSuccess()) (this as Success<T>).data else null
}
fun successResult() = DataResult.Success(null)
fun <T> successResult(data: T?) = DataResult.Success(data)
fun errorResult() = DataResult.Error<Nothing>(null)
This is the rest of my current implementation:
class NetworkClient(private val httpClient: HttpClient) {
private val baseUrl: String = "some url"
private val retrofit = Retrofit.Builder()
.baseUrl(mockend)
.addCallAdapterFactory(MyCallAdapterFactory())
.addConverterFactory(MoshiConverterFactory.create())
.client(httpClient.get())
.build()
private val apiService: ApiService = retrofit.create(StaApiService::class.java)
suspend fun <T> sendGet(endPoint: EndPoint, input: String): DataResult<T> {
val result = apiService.sendGetRequest<T>(endPoint.stringValue, queryMapOf(Pair("query", input)))
when (result) {
// do stuff here?
}
return result
}
}
interface ApiService {
#GET
suspend fun <T> sendGetRequest(
#Url url: String,
#QueryMap parameters: Map<String, String>): DataResult<T>
#GET
suspend fun <T> sendGetListRequest(
#Url url: String,
#QueryMap parameters: Map<String, String>): DataResult<List<T>>
}
abstract class CallDelegate<TIn, TOut>(
protected val proxy: Call<TIn>
) : Call<TOut> {
override fun execute(): Response<TOut> = throw NotImplementedError()
final override fun enqueue(callback: Callback<TOut>) = enqueueImpl(callback)
final override fun clone(): Call<TOut> = cloneImpl()
override fun cancel() = proxy.cancel()
override fun request(): Request = proxy.request()
override fun isExecuted() = proxy.isExecuted
override fun isCanceled() = proxy.isCanceled
abstract fun enqueueImpl(callback: Callback<TOut>)
abstract fun cloneImpl(): Call<TOut>
}
class ResultCall<T>(proxy: Call<T>) : CallDelegate<T, DataResult<T>>(proxy) {
override fun enqueueImpl(callback: Callback<DataResult<T>>) = proxy.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val code = response.code()
val result: DataResult<T> = if (code in 200 until 300) {
val body = response.body()
DataResult.Success(body)
} else {
DataResult.Error(code)
}
callback.onResponse(this#ResultCall, Response.success(result))
}
override fun onFailure(call: Call<T>, t: Throwable) {
val result: DataResult<Nothing> = if (t is IOException) {
DataResult.NetworkError
} else {
DataResult.Error(null)
}
callback.onResponse(this#ResultCall, Response.success(result))
}
})
override fun cloneImpl() = ResultCall(proxy.clone())
}
class ResultAdapter(
private val type: Type
) : CallAdapter<Type, Call<DataResult<Type>>> {
override fun responseType() = type
override fun adapt(call: Call<Type>): Call<DataResult<Type>> = ResultCall(call)
}
class MyCallAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
) = when (getRawType(returnType)) {
Call::class.java -> {
val callType = getParameterUpperBound(0, returnType as ParameterizedType)
when (getRawType(callType)) {
Result::class.java -> {
val resultType = getParameterUpperBound(0, callType as ParameterizedType)
ResultAdapter(resultType)
}
else -> null
}
}
else -> null
}
}
The above code is largely inspired by this answer to another question,
but I'm trying to add Generics to the mix, so I don't have to put every request into the interface by hand. Is it possible or not? I have tried for hours, also tried to build an adapter for the sealed class but failed. Has someone a good resource how this can be done?
As you can also see in the code I'd like to also be able to receive lists. Any tips here are much appreciated too.

Retrofit CallAdapter for suspending functions which returns for each List<Object> a List<LinkedTreeMap>. How to solve it?

I was inspired by the writing of this adapter to Valery Katkov's answer answer
My Retrofit call adapter is able to transform the JSON of normal objects correctly, but when I expect from a call a List<Object>, Retrofit returns me a List<LinkedTreeMap>. It cannot parse Object within the list
Exception
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.networkcalladapter.Post
CallAdapter Factory And CallAdapter
class NetworkCallAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
) = when (getRawType(returnType)) {
Call::class.java -> {
val callType = getParameterUpperBound(0, returnType as ParameterizedType)
when (getRawType(callType)) {
ResponseNetwork::class.java -> {
require(callType is ParameterizedType){ "resource must be paramterized" }
val resultType = getParameterUpperBound(0, callType)
ResponseNetworkAdapter<Any>(getRawType(resultType))
}
else -> null
}
}
else -> null
}
}
class ResponseNetworkAdapter<T: Any>(
private val type: Type
) : CallAdapter<T, Call<ResponseNetwork<T>>> {
override fun responseType() = type
override fun adapt(call: Call<T>): Call<ResponseNetwork<T>> = ResponseNetworkCall(call)
}
abstract class CallDelegate<TIn, TOut>(
protected val proxy: Call<TIn>
) : Call<TOut> {
override fun execute(): Response<TOut> = throw NotImplementedError()
final override fun enqueue(callback: Callback<TOut>) = enqueueImpl(callback)
final override fun clone(): Call<TOut> = cloneImpl()
override fun cancel() = proxy.cancel()
override fun request(): Request = proxy.request()
override fun isExecuted() = proxy.isExecuted
override fun isCanceled() = proxy.isCanceled
abstract fun enqueueImpl(callback: Callback<TOut>)
abstract fun cloneImpl(): Call<TOut>
}
class ResponseNetworkCall<T: Any>(proxy: Call<T>) : CallDelegate<T, ResponseNetwork<T>>(proxy) {
override fun enqueueImpl(callback: Callback<ResponseNetwork<T>>) {
proxy.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
callback.onResponse(this#ResponseNetworkCall, Response.success(ResponseNetwork.create(response)))
}
override fun onFailure(call: Call<T>, t: Throwable) {
callback.onResponse(this#ResponseNetworkCall, Response.success(ResponseNetwork.create(Exception(t))))
}
})
}
override fun cloneImpl() = ResponseNetworkCall(proxy.clone())
}
ResponseNetwork
sealed class ResponseNetwork<T> {
companion object {
fun <T> create(error: Exception): ResponseNetworkError<T> {
return ResponseNetworkError(error)
}
fun <T> create(response: Response<T>): ResponseNetwork<T> {
return if (response.isSuccessful) {
response.body()?.let {
ResponseNetworkSuccess(response.code(), response.headers(), it)
} ?: ResponseNetworkEmpty(
response.code(),
response.errorBody()?.string() ?: "unknown error"
)
} else {
val msg = response.errorBody()?.string()
ResponseNetworkError(Exception(msg))
}
}
}
}
data class ResponseNetworkSuccess<T>(
val code: Int,
val header: Headers,
val body: T
) : ResponseNetwork<T>()
data class ResponseNetworkEmpty<T>(
val code: Int,
val message: String
) : ResponseNetwork<T>()
data class ResponseNetworkError<T>(
val exception: Exception
) : ResponseNetwork<T>()
Remote Api
#GET("posts")
suspend fun getPost(): ResponseNetwork<List<Post>>
Retrofit
Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(NetworkCallAdapterFactory())
.build()
.create(RemoteApi::class.java)
Post Model
data class Post(val userId: Int,
val id: Int,
val title: String,
val body: String)
Someone understands why retrofit always comes back to me List<LinkedTreeMap> whenever I need a list from the network ?
can you replace your remote API with this and check it.
#GET("posts")
suspend fun getPost(): Deferred<Response<ResponseNetwork<List<Post>>>
i fixed my bug in NetworkCallAdapterFactory
ResponseNetworkAdapter<Any>((resultType))

Categories

Resources