Retrofit does not close connection where seems it should - android

After the timeout the following code
#Throws(ApiException::class)
inline fun <reified R> execute(call: Call<R>): R {
var response: Response<R>? = null
try {
response = call.execute() //line (main.kt:137) THE ONE STACKTRACE POINTS TO!!
} catch (e: IOException) {
//SHOULD close connection
throw ApiException.NetworkError(response?.errorBody()?.string() ?: DEFAULT_REASON, cause = e)
}
if (response != null && response.isSuccessful)
try {
return response.body()!!
} catch (e: Exception) {
response.errorBody()?.close()
}
//SHOULD close connection
throw ApiException.ServiceRespondedNegative(response.errorBody()?.string() ?: DEFAULT_REASON)
}
throws
java.lang.Throwable: response.body().close()
at okhttp3.internal.platform.Platform.getStackTraceForCloseable(Platform.java:148)
at okhttp3.RealCall.captureCallStackTrace(RealCall.java:89)
at okhttp3.RealCall.execute(RealCall.java:73)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:180)
at com.example.client.functional.Api$analyze$1$doResume$$inlined$get$1$1.invoke(main.kt:137)
at com.example.client.functional.MainKt$runInAsyncContext$1$1.doResume(main.kt:97)
at kotlin.coroutines.experimental.jvm.internal.CoroutineImpl.resume(CoroutineImpl.kt:42)
at kotlinx.coroutines.experimental.DispatchedTask$DefaultImpls.run(Dispatched.kt:150)
at kotlinx.coroutines.experimental.DispatchedContinuation.run(Dispatched.kt:14)
at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
I've tried lots of combinations of try/catch/finally
The stacktrace points to execute() method call at (main.kt:137)
Don't know how and where to close body.
tried call raw().close(), string() all over the place,
still the same error.

The answer is
#Throws(ApiException::class)
inline fun <reified R> execute(call: Call<R>): R {
var response: Response<R>? = null
try {
response = call.execute() //line (main.kt:137) THE ONE STACKTRACE POINTS TO!!
} catch (e: IOException) {
//SHOULD close connection
throw ApiException.NetworkError(response?.errorBody()?.string() ?: DEFAULT_REASON, cause = e)
}
if (response != null && response.isSuccessful)
try {
return response.body()!!
} catch (e: Exception) {
response.errorBody()?.close()
}
else response.close() // something like this line was forgotten.
//SHOULD close connection
//throw ApiException.ServiceRespondedNegative(response.errorBody()?.string() ?: DEFAULT_REASON)
}

Related

LruCache in unit test return null

It's a bit wired when init Lrucache, it throws NullPointerException.
The code is:
#Test
fun testLruCache() {
try {
val code = "code"
val cache: LruCache<String, Int> = LruCache(1000)
println(cache)
Assert.assertNotNull(cache)
cache.put(code, 1)
val getValue = cache.get(code)
Assert.assertEquals(1, getValue)
println("try end")
} catch (e: Exception) {
println(e)
println("catch end")
}
}
And the output is:
java.lang.NullPointerException
catch end
The line println(cache) even without called, the LruCache throw the exception. So does the behavior in unit test is different?

Kotlin coroutines resumeWithException error

I decided to wrap getting device location (once, without updating) using kotlin coriutines, so finally i got this code:
#SuppressLint("MissingPermission")
suspend fun LocationManager.getCurrentLocationOnce(): Location {
return suspendCancellableCoroutine { continuation ->
try {
val locationListener = object : SimpleLocationListener {
override fun onLocationChanged(location: Location?) {
if (location == null) {
this#getCurrentLocationOnce.removeUpdates(this)
continuation.resumeWithException(FailedToRetrieveLocationException("Location is NULL"))
} else {
this#getCurrentLocationOnce.removeUpdates(this)
continuation.resume(location)
}
}
override fun onProviderEnabled(provider: String?) {}
override fun onProviderDisabled(provider: String?) {
this#getCurrentLocationOnce.removeUpdates(this)
continuation.resumeWithException(ProviderDisabledException(provider ?: ""))
}
}
this.requestSingleUpdate(
LocationManager.GPS_PROVIDER,
locationListener,
null
)
} catch (e : Exception) {
continuation.resumeWithException(e)
}
}
}
When GPS is ON all works fine, but when GPS is OFF program fails with exception ProviderDisabledException, thats because of:
override fun onProviderDisabled(provider: String?) {
this#getCurrentLocationOnce.removeUpdates(this)
continuation.resumeWithException(ProviderDisabledException(provider ?: ""))
}
But i don't know why it's fails, because in place where i'm using this function i've got:
try {
val locationManager = (requireActivity().getSystemService(Context.LOCATION_SERVICE) as? LocationManager)
?: throw FailedToRetrieveLocationException("Location Service is null")
val location = locationManager.getCurrentLocationOnce()
log("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle",
"Successfully got location={lat:${location.latitude}, long:${location.longitude}}")
downloadRestaurantsWithLocation(location)
} catch (ex : FailedToRetrieveLocationException) {
logError("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle", ex)
throw ex
} catch (providerException : ProviderDisabledException) {
logError("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle", providerException)
throw providerException
} catch (e : Exception) {
logError("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle", e)
throw e
}
So i'm logging exception and rethrow it to caller function and in caller function i'm catching this exception:
try {
log("[GOOGLE] downloadRestaurants", "Starting donwload restaurants for GOOGLE")
downloadRestaurantsWithGPSLocationGoogle()
} catch (e : Exception) {
logError("[GOOGLE] error happened while getting location", e)
downloadRestaurantsWithFusedLocationGoogle()
}
And in error stacktrace i've got only this:
E/[GOOGLE] downloadRestaurantsWithGPSLocationGoogle: my.package.location.exceptions.ProviderDisabledException: Provider gps disabled
at my.package.common.location.LocationUtilsKt$getCurrentLocationOnce$$inlined$suspendCancellableCoroutine$lambda$1.onProviderDisabled(LocationUtils.kt:45)
at android.location.LocationManager$ListenerTransport._handleMessage(LocationManager.java:384)
at android.location.LocationManager$ListenerTransport.access$000(LocationManager.java:300)
at android.location.LocationManager$ListenerTransport$1.handleMessage(LocationManager.java:316)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:6878)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:876)
I don't know why app fails, because code like this works perfect:
lifecycleScope.launch {
try {
throwError()
} catch (e : Exception) {
e.printStackTrace()
}
}
private suspend fun throwError() {
return suspendCancellableCoroutine { continuation ->
continuation.resumeWithException(ProviderDisabledException("TEST"))
}
}
So, finally i realized why it's crash the app =). All ok with coroutines.
Problem is in this method:
#Throws(ProviderDisabledException::class, FailedToRetrieveLocationException::class)
private fun downloadRestaurantsWithGPSLocationGoogle() = lifecycleScope.launch {
log("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle", "Trying to get location via GPS")
try {
val locationManager = (requireActivity().getSystemService(Context.LOCATION_SERVICE) as? LocationManager)
?: throw FailedToRetrieveLocationException("Location Service is null")
val location = locationManager.getCurrentLocationOnce()
log("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle",
"Successfully got location={lat:${location.latitude}, long:${location.longitude}}")
downloadRestaurantsWithLocation(location)
} catch (ex : FailedToRetrieveLocationException) {
ex.printStackTrace()
logError("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle", ex)
throw ex
} catch (providerException : ProviderDisabledException) {
providerException.printStackTrace()
logError("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle", providerException)
throw providerException
} catch (e : Exception) {
e.printStackTrace()
logError("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle", e)
throw e
}
}
And the problem is that i'm throwing exception from coroutine and handle this exception not in coroutine, so i'm launched my coroutine and all try-cathces are skipped, because here i'm using fire and forget style. So to fix this i need to do this method suspend and throw exceptions.
Place where trying to catch errors:
private fun downloadRestaurants() = lifecycleScope.launch {
log("downloadRestaurantsWithLocationSort",
"Requesting Manifest.permission.ACCESS_COARSE_LOCATION & Manifest.permission.ACCESS_FINE_LOCATION permissions")
val user = requestPermissions(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
)
if (!user.any { !it.second }) {
// permission is granted, can download restaurants and sort by nearest
log("downloadRestaurantsWithLocationSort", "Permissions is granted")
log("MANUFACTURER", Build.MANUFACTURER)
if (Build.MANUFACTURER == "Huawei" || Build.MANUFACTURER == "HUAWEI") {
showToast("HUAWEI")
try {
log("[HUAWEI] downloadRestaurants", "Starting donwload restaurants for HUAWEI")
downloadRestaurantsWithGPSLocationHuawei()
} catch (e : Exception) { // this will not work, because FIRE and FORGET
e.printStackTrace()
logError("[HUAWEI] error happened while getting location", e)
mainViewModel.downloadRestaurantsHeaders(null)
}
} else {
showToast("NOT A HUAWEI")
try {
log("[GOOGLE] downloadRestaurants", "Starting donwload restaurants for GOOGLE")
downloadRestaurantsWithGPSLocationGoogle()
} catch (e : Exception) { // this will not work, because FIRE and FORGET
e.printStackTrace()
logError("[GOOGLE] error happened while getting location", e)
downloadRestaurantsWithFusedLocationGoogle()
}
}
} else {
// permission is not granted, just download the restaurants
log("downloadRestaurantsWithLocationSort", "Permissions is NOT granted")
mainViewModel.downloadRestaurantsHeaders(null)
}
}
So the answer make functions downloadRestaurantsWithGPSLocationGoogle and downloadRestaurantsWithFusedLocationGoogle suspend and don't launch separate coroutine inside them. (remove lifecycleScope.launch)

Try block throws exception, but catch block does not catch it

I am using google login and I'm getting an exception. However, it looks like I'm unable to catch it.
I have the following code:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
RC_SIGN_IN -> {
val task = try{
GoogleSignIn.getSignedInAccountFromIntent(data)
} catch (e: ApiException) {
Log.d("EXCEPTION", e.message)
when(e.statusCode) {
12501 -> return
else -> {
e.printStackTrace()
return
}
}
}
...
From Logcat I can see, that ApiException is the right Exception to catch:
And its definetly the one I am catching:
At first I thought the direct assignment with val task = try... might be responsible for this for some reason, so I changed my code accordingly and tried this:
var task: Task<GoogleSignInAccount>?
try{
task = GoogleSignIn.getSignedInAccountFromIntent(data)
} catch (e: ApiException) {
Log.d("EXCEPTION", e.message)
when(e.statusCode) {
12501 -> task = null
else -> {
e.printStackTrace()
task = null
}
}
}
Still, same behavior. So I thought maybe there is another exception being thrown, so I added another catch block:
var task: Task<GoogleSignInAccount>?
try{
task = GoogleSignIn.getSignedInAccountFromIntent(data)
} catch (e: ApiException) {
Log.d("EXCEPTION", e.message)
when(e.statusCode) {
12501 -> task = null
else -> {
e.printStackTrace()
task = null
}
}
} catch (e: Exception) {
Log.d("EXCEPTION", e.message)
task = null
}
Still the same behavior. I have spotted this code with breakpoints on every step but it just ignores the catch block completely. I stop at the try block:
But if I resume, the next step is outside of my try-catch block.
Can someone explain that behavior?
Just as I asked this question, I realized something:
The catch block is ignored because the try block doesn't really throw an exception.
The task object is set and its the one that holds the exception. When the task object is used later like this: val account = task.getResult(ApiException::class.java) then that's where the exception is actually thrown. At least that's what I expect here. The exception itself is basically caught, kept and thrown at a later point in time, but with the old stacktrace...
Not 100% sure about the inner workings of this, but that's what I could tell from logcat and my code.
So, instead of a try-catch, I simply went and handled the exception manually:
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
if(task.exception != null) {
val e = task.exception as? ApiException
if(e != null) {
when(e.statusCode) {
12501 -> return
else -> {
e.printStackTrace()
return
}
}
} else {
task.exception?.printStackTrace()
}
return
}

Unable to get error response in error body of retrofit

I am unable to get 400 response in error body of retrofit. I have set logging level its showing in logs but not showing in error body i have searched a lot but didn't find any solution is anyone there who help me in this case to get rid of this problem
call_.enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>?, response: Response<ResponseBody>?) {
if (response?.code() == 400) {
var jObjError: JSONObject? = null
try {
var jObjErrorr = response.errorBody().string()
CustomLogs.displayLogs("$TAG jObjErrorr: $jObjErrorr")
} catch (e: Exception) {
}
try {
val string = jObjError?.getstring("error_description")
CustomLogs.displayLogs("$TAG jObjError: $string")
} catch (e: Exception) {
e.printStackTrace();
}
}
}
i need error body to get and display message and my log shows this
{"error":"Authorize","error_description":"Error in authentication"}
but error body is not showing this object
As IntelliJ Amiya mentioned in comment to your original post you should do this in onFailure method. As far as I know Retrofit's onResponse will not be called in cases of response code not in 200 range (200, 201, 202 etc.) so your check for if (response?.code() == 400) will never return true.
If you go through the Retrofit onResponse Library...,it's clearly mentioned that Retrofit does not create Body for response with status code below 200 or above 300.You have to specify your error Response Specifically!!
Decided to add it as separate answer:
if (response?.code() == 400) {
var jObjError: JSONObject? = null
try {
jObjError = response.errorBody().string()
CustomLogs.displayLogs("$TAG jObjError: $jObjError")
} catch (e: Exception) {
}
try {
val string = jObjError?.optString("error_description")
CustomLogs.displayLogs("$TAG jObjError: $string")
} catch (e: Exception) {
e.printStackTrace();
}
}
Could you try this fragment?
you can do this in Kotlin:
val errorResponse: ErrorMessage? = Gson().fromJson(
response.errorBody()!!.charStream(),
object : TypeToken<ErrorMessage>() {}.type
)

Handle no internet connection error of retrofit 2.6 with kotlin coroutines

I'm using retrofit 2.6 with kotlin coroutines to make API call without block the UI thread, I got it work but the app crashes when I switch off the internet connection. The logcat error is: E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1
Here is my code:
private fun handleIntent(slug: String) {
val service = UtilityMethods.migrationTimeService()
UtilityMethods.showView(loading_view)
UtilityMethods.hideView(network_error_msg)
CoroutineScope(Dispatchers.IO).launch {
val res = service.getPostBySlug(slug)
try {
withContext(Dispatchers.Main) {
//Do something with response e.g show to the UI.
val post = res.body()!!.first()
UtilityMethods.hideView(loading_view)
val title = post.title?.rendered
val content = post.content?.rendered
val imageUrl = post.jetPackFeaturedMediaUrl
title_txtView.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
Html.fromHtml(title, Html.FROM_HTML_MODE_COMPACT).toString()
else
Html.fromHtml(title).toString()
content_txtView.loadData(content.toString(), "text/html", "UTF-8")
Picasso.get().load(imageUrl).fit().centerCrop().into(thumbnail_imgview)
}
} catch (e: HttpException) {
UtilityMethods.showView(network_error_msg)
} catch (e: Throwable) {
Toast.makeText(this#PostContentActivity, "Ooops: Something else went wrong", Toast.LENGTH_LONG)
}
}
}
I've got the code working, the new code is:
private fun handleIntent(slug: String) = GlobalScope.launch(Dispatchers.Main) {
val service = UtilityMethods.migrationTimeService()
UtilityMethods.showView(loading_view)
UtilityMethods.hideView(network_error_msg)
try {
val res = withContext(Dispatchers.IO) {
service.getPostBySlug(slug)
}
//Do something with response e.g show to the UI.
val post = res.body()!!.first()
UtilityMethods.hideView(loading_view)
val title = post.title?.rendered
val content = post.content?.rendered
val imageUrl = post.jetPackFeaturedMediaUrl
title_txtView.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
Html.fromHtml(title, Html.FROM_HTML_MODE_COMPACT).toString()
else
Html.fromHtml(title).toString()
content_txtView.loadData(content.toString(), "text/html", "UTF-8")
Picasso.get().load(imageUrl).fit().centerCrop().into(thumbnail_imgview)
}
catch (e: HttpException) {
Toast.makeText(this#PostContentActivity, "Exception ${e.message}", Toast.LENGTH_LONG).show()
}catch (e: IOException) {
UtilityMethods.hideView(loading_view)
UtilityMethods.showView(network_error_msg)
} catch (e: Throwable) {
Toast.makeText(this#PostContentActivity, "Ooops: Something else went wrong ${e.message}", Toast.LENGTH_LONG).show()
}
}
So while looking into stacktrace I found that ConnectException is thrown when network is unavailable
And that's how I do it in kotlin and it works for me,
suspend fun<T: Any> safeAPICall(call: suspend () -> Response<T>) : T{
val response = try {
call.invoke()
}
catch (e:java.lang.Exception){
e.printStackTrace()
val message = if( e is ConnectException) "Connection Error" else "Something went wrong. Please try again."
throw IOException(ResponseError(message, 500).convertToJsonString())
}
// When connection is OK
if(response.isSuccessful){
return response.body()!!
}else{
val error = response.errorBody()?.string()
error?.let{
val message = JSONObject(it).optString("message", "Something went wrong")
val responseError = ResponseError(message, response.code())
throw IOException(responseError.convertToJsonString())
}
throw IOException(ResponseError("Something went wrong. Please try again.", 500).convertToJsonString())
}
}
The data class that I use
data class ResponseError(val message:String, val errorCode:Int)
Usage:
try {
val response = safeAPICall {APIClient.planner.viewSites(view.context.authToken)}
}
catch (e:Exception){
view.snack(e.message?.toModel<ResponseError>()?.message?: unspecified_error)
}
Bonus:
inline fun <reified T> JSONObject.toModel(): T? = this.run {
try {
Gson().fromJson<T>(this.toString(), T::class.java)
}
catch (e:java.lang.Exception){ e.printStackTrace(); null }
}
inline fun <reified T> String.toModel(): T? = this.run {
try {
JSONObject(this).toModel<T>()
}
catch (e:java.lang.Exception){ null }
}
Instead of this:
CoroutineScope(Dispatchers.IO).launch {
val res = service.getPostBySlug(slug)
try {
withContext(Dispatchers.Main) {
Try this one:
CoroutineScope(Dispatchers.Main).launch {
val res = service.getPostBySlug(slug)
withContext(Dispatchers.IO) {
try {
wrap your 'try and catch' block code within Dispatchers.IO instead of wraping your Dispatchers.IO with in yout try block

Categories

Resources