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.
Related
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 am making an api call using retrofit and I want to write a unit test to check if it returns an exception.
I want to force the retrofit call to return an exception
DataRepository
class DataRepository #Inject constructor(
private val apiServiceInterface: ApiServiceInterface
) {
suspend fun getCreditReport(): CreditReportResponse {
try {
val creditReport = apiServiceInterface.getDataFromApi() // THIS SHOULD RETURN AN EXCEPTION AND I WANT TO CATCH THAT
return CreditReportResponse(creditReport, CreditReportResponse.Status.SUCCESS)
} catch (e: Exception) {
return CreditReportResponse(null, CreditReportResponse.Status.FAILURE)
}
}
}
ApiServiceInterface
interface ApiServiceInterface {
#GET("endpoint.json")
suspend fun getDataFromApi(): CreditReport
}
I have written a test case for getCreditReport which should validate the failure scenario
#Test
fun getCreditReportThrowException() {
runBlocking {
val response = dataRepository.getCreditReport()
verify(apiServiceInterface, times(1)).getDataFromApi()
Assert.assertEquals(CreditReportResponse.Status.FAILURE, response.status)
}
}
so to make the above test case pass, I need to force the network call to throw and exception
please suggest
Thanks
R
Actually #Vaibhav Goyal provided a good suggestion to make your testing as easier. Assuming you are using MVVM structure, in your test cases you can inject a "mock" service class to mock the behaviours that you defined in the test cases, so the graph will be like this
Since I am using mockk library at the moment, the actual implementation in your code base would be a little bit different.
#Test
fun test_exception() {
// given
val mockService = mockk<ApiServiceInterface>()
val repository = DataRepository(mockService)
every { mockService.getDataFromApi() } throws Exception("Error")
// when
val response = runBlocking {
repository.getCreditReport()
}
// then
verify(exactly = 1) { mockService.getDataFromApi }
assertEquals(CreditReportResponse.Status.FAILURE,response.status)
}
But if you want to test the exception thrown from Retrofit, then you might need mockServer library from square to help you to achieve this https://github.com/square/okhttp#mockwebserver
And the graph for this would be like this
You also have to setup the mock server to do so
#Test
fun test_exception_from_retrofit() {
// can put in the setup method / in junit4 rule or junit5 class
val mockWebServer = MockWebServer()
mockWebServer.start()
// given
val service = Retrofit.Builder()
.baseUrl(mockWebServer.url("/").toString())
.build()
.create(ApiServiceInterface::class)
val repository = DataRepository(service)
// when
mockWebServer.enqueue(MockResponse()
.setResponseCode(500)
.setBody("""{"name":"Tony}""") // you can read the json file content and then put it here
)
val response = runBlocking {
repository.getCreditReport()
}
// then
verify(exactly = 1) { mockService.getDataFromApi }
assertEquals(CreditReportResponse.Status.FAILURE,response.status)
// can put in tearDown / in junit4 rule or juni5 class
mockWebServer.shutdown()
}
SO you can test different exception like json format invalid, 500 status code,data parsing exception
Bonus point
Usually I would put the testing json under test directory and make it almost same as the api path for better maintainence
I'm trying to make a GET request to my server from my Android application using Retrofit, OKHttp, and Kotlin Flow w/ MVVM architecture.
For some reason whenever I try to invoke my GET request from a try-catch scope the program always enters catch, but the value of Throwable is always null. It's as if there was a crash but no exception being thrown for me to examine in the logs/debugger.
Filtering logcat for OKHTTP logs I can see that the network request never occurs/reaches the server. It seems to be failing locally, somewhere in my device's app process, before triggering the catch block.
Other network calls execute fine in this same project, so something about my specific implementation for this one must be incorrect. What am I missing?
RemoteDataSource.kt
suspend fun getProductData(skuId: String): Result<ProductLookupResponse>{
return getResponse(
request = {
pickingAPI.lookupProductBySku(
"Test User",
"A place",
skuId
)
},
defaultErrorMessage = "Error looking up product"
)
}
private suspend fun <T> getResponse(
request: suspend () -> Response<T>,
defaultErrorMessage: String
): Result<T> {
return try {
val result = request.invoke() //Always Crashes here for this request
if (result.isSuccessful) {
Result.success(result.body())
} else {
val networkError = NetworkError(code = result.code(), message = result.message())
Result.error(message = networkError.message ?: "", error = networkError)
}
} catch (e: Exception) {
Result.error(defaultErrorMessage, null)
}
}
Repo.kt
suspend fun getProductData(
skuId: String
): Flow<Result<ProductLookupResponse>> {
return flow {
emit(Result.loading())
emit(RemoteDataSource.getProductData(skuId))
}.flowOn(Dispatchers.IO)
}
API.kt
#GET("garments/sku/{skuId}")
fun lookupProductBySku(
#Header(HEADER_ASSOCIATE_ID) userUniqueId: String,
#Header(HEADER_LOCATION_ID) dcId: String,
#Path("skuId") sku: String
): Response<ProductLookupResponse>
I've spent hours trying to figure this thing out, and I still can figure it out.
I'm trying to retrieve data from a website using JSON.
If the website is live and everything, it works, but if the website returns something else than the data, like a 403 error, or any other error, then it crashes. I tried to debug it, but I still don't understand what is going on here.
Here is my code:
I have a NetworkModule with an interceptor that is supposed to check is the response is valid or not, and from what I can tell it works, because my variable isDataRetrievable is false (the value by default):
val networkModule = module {
single {
val customGson =
GsonBuilder().registerTypeAdapter(Lesson::class.java, LessonDeserializer())
.create()
Retrofit.Builder()
.client(get())
.addConverterFactory(
GsonConverterFactory.create(customGson)
)
.baseUrl(BuildConfig.URL)
.build()
}
factory {
OkHttpClient.Builder()
.addInterceptor(Interceptor { chain ->
chain.withConnectTimeout(1,TimeUnit.SECONDS)
val request: Request = chain.request()
val response = chain.proceed(request)
if (response.isSuccessful){
networkStatus.isDataRetrievable = true
}
response
}).build()
}
factory {
get<Retrofit>().create(LessonApi::class.java)
}
}
Next, I have my API to get the data:
interface LessonApi {
#GET("/JSON/json_get_data.php")
suspend fun getLessons(): Call<Lesson>
}
Then, for some reason, I have a repository (I'm not the only one working on this code, I didn't do this part):
class LessonRepository(private val service: LessonApi) {
suspend fun getLessons() = service.getLessons()
}
Then, I have my splash screen view model, that is supposed to retrieve the data if possible:
if (networkStatus.isNetworkConnected && networkStatus.isWebsiteReachable) {
var tmp = repository.getLessons()
tmp.enqueue(object : Callback<Lesson> {
override fun onFailure(call: Call<Lesson>, t: Throwable) {
Log.d("DataFailure",t.message.toString())
nextScreenLiveData.postValue(false)
}
override fun onResponse(call: Call<Lesson>, response: Response<Lesson>) {
Log.d("DataFailure","Test")
}
})
}else{
nextScreenLiveData.postValue(false)
}
The problem is that when the program get to the line repository.getLessons(), it crashes with the error:
retrofit2.HttpException: HTTP 403
at retrofit2.KotlinExtensions$await$2$2.onResponse(KotlinExtensions.kt:49)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:129)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
So onFailure or onResponse are never called. I tried to run the debugger, to step in, but I cannot figure it out when it fails.
I thought it was because it was trying to deserialize invalid data, but I put breakpoints everywhere in my deserializer, and it never hits the breakpoints.
I'm not a professional android developer, but I'm very confused here.
What I'd like to do is that if the request is unsuccessful, just discard the response (do not deserialize it), and display a message or exit.
Please help, it's so frustrating. I'm not sure how to intercept errors or what to do if Interceptors get an unsuccessful request (for now I just set a variable but it's unused).
Thanks.
Edit: What I'm trying to do, is to retrieve data from a webserver. If it cannot (for any reason), I don't want the gson to parse data (because it will probably be garbage and will not correspond to my deserializer). However, I feel like this okhttp / retrofit is a pipeline, where okhttp get the response from the webserver and pass it to a gson converter. What I want to do is intercept this response, and if it's not successful, to NOT pass it to gson, set a variable, so that the rest of my application knows what to do. But the thing is, for now, it just crash even before it gets to the callback in enqueue. The interceptor works just fine, except I'd like him to drop the response if it's not successful. Is it possible?
I tried something like that, and it worked to handle bad codes (>400), but I also wanted to handle malformed JSON data, so I added the onResponse and onFailure callbacks, but it never worked, because when I receive a malformed JSON, it also trigger an exception, and then go in the catch before it can go on the 'enqueue', so I'm not sure what this is used for.
try {
val lessons = repository.getLessons().enqueue(object : Callback<List<Lesson>> {
override fun onResponse(call: Call<List<Lesson>>, response: Response<List<Lesson>>) {
networkStatus.isDataRetrievable = response.isSuccessful
Log.d("Retrofit", "Successful response")
nextScreenLiveData.postValue(response.isSuccessful)
}
override fun onFailure(call: Call<List<Lesson>>, t: Throwable) {
Log.d("Retrofit", "Failure response")
nextScreenLiveData.postValue(false)
}
})
nextScreenLiveData.postValue(true)
} catch (e: Exception) {
nextScreenLiveData.postValue(false)
}
Anyway, just this code works for everything in the end:
try {
val lessons = repository.getLessons().filter {
it.lesson.contains("video")
}.filter {
DataUtils.isANumber(it.id)
}
lessonDao.insertLessons(lessons)
networkStatus.isDataRetrievable = true
} catch (e: Exception) {
networkStatus.isDataRetrievable = false
}
But in my API, I don't return callbacks, I directly return the objects, as such:
#GET("/JSON/json_get_dat.php")
suspend fun getLessons(): List<Lesson>
I don't know if this is the right way to do it, but it works. I hope this might help others.
I am new to Android development with Kotlin and I am struggling on finding any useful documentation on how to create a simple GET and POST requests with the best current practices as possible. I am coming from an Angular development and there we used a reactive development using RxJS.
Normally I would create a service file that would hold all my request functions, then I would use this service in whichever component and subscribe to the observable.
How would you do this in Android? Is there a good started example of things that have to be created. From the first look, everything looks so complicated and over-engineered
I suggest you to use the official recommendation of OkHttp, or the Fuel library for easier side and it also has bindings for deserialization of response into objects using popular Json / ProtoBuf libraries.
Fuel example:
// Coroutines way:
// both are equivalent
val (request, response, result) = Fuel.get("https://httpbin.org/ip").awaitStringResponseResult()
val (request, response, result) = "https://httpbin.org/ip".httpGet().awaitStringResponseResult()
// process the response further:
result.fold(
{ data -> println(data) /* "{"origin":"127.0.0.1"}" */ },
{ error -> println("An error of type ${error.exception} happened: ${error.message}") }
)
// Or coroutines way + no callback style:
try {
println(Fuel.get("https://httpbin.org/ip").awaitString()) // "{"origin":"127.0.0.1"}"
} catch(exception: Exception) {
println("A network request exception was thrown: ${exception.message}")
}
// Or non-coroutine way / callback style:
val httpAsync = "https://httpbin.org/get"
.httpGet()
.responseString { request, response, result ->
when (result) {
is Result.Failure -> {
val ex = result.getException()
println(ex)
}
is Result.Success -> {
val data = result.get()
println(data)
}
}
}
httpAsync.join()
OkHttp example:
val request = Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build()
// Coroutines not supported directly, use the basic Callback way:
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) throw IOException("Unexpected code $response")
for ((name, value) in response.headers) {
println("$name: $value")
}
println(response.body!!.string())
}
}
})
you can use something like that:
internal inner class RequestTask : AsyncTask<String?, String?, String?>() {
override fun doInBackground(vararg params: String?): String? {
val httpclient: HttpClient = DefaultHttpClient()
val response: HttpResponse
var responseString: String? = null
try {
response = httpclient.execute(HttpGet(uri[0]))
val statusLine = response.statusLine
if (statusLine.statusCode == HttpStatus.SC_OK) {
val out = ByteArrayOutputStream()
response.entity.writeTo(out)
responseString = out.toString()
out.close()
} else {
//Closes the connection.
response.entity.content.close()
throw IOException(statusLine.reasonPhrase)
}
} catch (e: ClientProtocolException) {
//TODO Handle problems..
} catch (e: IOException) {
//TODO Handle problems..
}
return responseString
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
//Do anything with response..
}
}
and for call:
RequestTask().execute("https://v6.exchangerate-api.com/v6/")
HttpClient is not supported any more in sdk 23. You have to use URLConnection or downgrade to sdk 22 (compile 'com.android.support:appcompat-v7:22.2.0')
If you need sdk 23, add this to your gradle:
android {
useLibrary 'org.apache.http.legacy'
}
You also may try to download and include HttpClient.jar directly into your project or use OkHttp instead
The best practice you ever get to just go through the basics of networking call and create some demo applications using Android Studio.
If you want to click start then follow this tutorial
Simplet netwroking call in Kotlin
https://www.androidhire.com/retrofit-tutorial-in-kotlin/
Also, I would like to suggest Please create some demo application for GET and POST request and then merge these examples into your project.