Gabriel Morin
Gabriel Morin

Reputation: 2190

Retrofit and Moshi: how to handle com.squareup.moshi.JsonDataException

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?

Upvotes: 6

Views: 1256

Answers (2)

quealegriamasalegre
quealegriamasalegre

Reputation: 3258

While the answer by @XME is technically correct I find that this article provides a more elegant and generalizable way of handling all kind of errors stemming from okHttp, moshi or retrofit by using a callAdapter.

Upvotes: 0

XME
XME

Reputation: 525

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.

Upvotes: 1

Related Questions