I'm using okHttp in Kotlin, but when the server is not running it throws an exception and crashes my app. I want
E/AndroidRuntime: FATAL EXCEPTION: Thread-5
Process: com.mkapps.cvgen, PID: 24497
java.io.IOException: Unexpected code Response{protocol=http/1.1, code=500, message=INTERNAL SERVER ERROR, url=http://192.168.8.103:5000/name}
at com.mkapps.cvgen.fragments.CreateFragment.fetchGithubInfo(CreateFragment.kt:193)
at com.mkapps.cvgen.fragments.CreateFragment.access$fetchGithubInfo(CreateFragment.kt:26)
at com.mkapps.cvgen.fragments.CreateFragment$BackgroundFetcher.run(CreateFragment.kt:217)
at java.lang.Thread.run(Thread.java:784)
val request = Request.Builder().url("http://192.168.8.103:5000/name").post(formBody).build()
val response = client.newCall(request).execute()
if (!response.isSuccessful){
throw IOException("Unexpected code $response")
}else if (response.isSuccessful){
getUrlAsync(NRC)
}
return response.body!!.string()
If you would like to notify the user that they don't have access to the server, you can do so in this way:
inner class BackgroundFetcher : Runnable {
override fun run() { // (3)
try {
val github_info = fetchGithubInfo()
println(github_info)
} catch (e: IOException) {
this#CreateFragment.requireActivity().runOnUiThread {
Toast.makeText(
this#CreateFragment.requireActivity(),
"Make sure you have access to the server",
Toast.LENGTH_SHORT
).show()
}
}
}
}
runOnUiThread is needed because BackgroundFetcher is run on a background thread, and showing a toast is only possible on the UI thread.
Related
I call network operations like this.
viewModelScope.launch {
try {
val data = withContext(dispatcher.io()) { homeRepository.getData() }
} catch (e: Throwable) {
val error = globalErrorHandler.getMessageForUser(throwable)
Timber.d("seriesResponse failed: $error")
}
}
and the response model is
data class MainResponse<T>(
val list: List<T> = emptyList(),
val total: Int?
)
but it still throw the following error.
Fatal Exception: com.squareup.moshi.JsonDataException
Non-null value 'credits' was null at $.data[1].credits
What's wrong with my code? I think it should catch the error.
UPDATED
This is my mistake, this line val error = globalErrorHandler.getMessageForUser(throwable) cause the error, I forgot to handle it.
This is my mistake, this line val error = globalErrorHandler.getMessageForUser(throwable) cause the error, I forgot to handle it.
The problem is getting buckets on the S3 cloud. The most interesting thing here is that everything works fine on most phones, but not on all models and mainly on Samsung models (there are also Xiaomi and other models). Unfortunately, I'm not very familiar with the service. Is it possible to track requests in the service itself and understand why it gives a 403 error?
Here is the exception I am getting:
Non-fatal Exception: retrofit2.HttpException: HTTP 403 Forbidden
at retrofit2.KotlinExtensions$await$2$2.onResponse(KotlinExtensions.kt:53)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:129)
at okhttp3.RealCall$AsyncCall.run(RealCall.kt:140)
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:923)
Here is the method to sign and send keys:
class AwsSignInterceptor(val signer: OkHttpAwsV4Signer) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
.addHeader("host", getHostHeader())
.addHeader("x-amz-date", getXAmzDateheader())
.addHeader("x-amz-content-sha256", "".sha256())
.addHeader("Range", "")
.build()
val signedRequest = signer.sign(request, S3_OBJECT_STORAGE_ACCESS_KEY, S3_OBJECT_STORAGE_SECRET_KEY)
FirebaseCrashlytics.getInstance().log(signedRequest.toString())
return chain.proceed(signedRequest)
}
And here is the method where this exception is thrown:
try {
api.getAlbum("$albumIdInt").downloadToFileWithProgress(albumIdInt)
.collect { download ->
when (download) {
is Download.Progress -> {
notifyWithProgress(download.percent)
}
is Download.Finished -> {
val zip = download.file
val result = mainInteractor.saveAlbum(albumIdInt.toString(), zip)
infoInteractor.update(albumIdInt.toString())
MainScope().launch {
if (result) {
notifyWithFinish()
} else {
notifyWithError()
FirebaseCrashlytics.getInstance().log("Error during saving file")
}
}
}
}
}
} catch (e: Exception) {
MainScope().launch {
Log.e("http", "${e.message}")
notifyWithError()
FirebaseCrashlytics.getInstance().recordException(e)
}
}
view model :
val link: String? = null
if(link.isNullOrEmpty()){
throw IllegalArgumentException(mApplication.resources.getString(R.string.server_in_maintenance_please_try_again_later))
}
fargment :
try{
//calling the viewmodel function
}catch (e:IllegalArgumentException){
Toast.makeText(requireContext(), e.localizedMessage, Toast.LENGTH_SHORT).show()
}catch (e:Exception){
Toast.makeText(requireContext(), e.localizedMessage, Toast.LENGTH_SHORT).show()
}
I assigned null into the "link" to demonstrate calling from the API when the API's server is in maintenance or shut down temporarily..
this is the stack trace -
E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-4
Process: com.amagen.supercheap, PID: 12491
java.lang.IllegalArgumentException: MY MESSAGE FROM RESOURCE FILE
at com.amagen.supercheap.MainActivityViewModel$createSuperItemsTable$2$1.invokeSuspend(MainActivityViewModel.kt:112)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
You need to add CoroutineExceptionHandler where you a starting Coroutine and inside that exceptionHandler variable you will get the exception details. It will also prevent app from crashing.
Sample code below
private val exceptionHandler = CoroutineExceptionHandler { context, exception ->
exception.message?.let { Log.d("Error", it) }
}
lifecycleScope.launch(kotlinx.coroutines.Dispatchers.IO + exceptionHandler) {
}
Why don't you catch errors in coroutines this way?
private val dispatcher: CoroutineDispatcher = Dispatchers.Default
private val coroutineScope = CoroutineScope(dispatcher)
private val handler = CoroutineExceptionHandler { _, _ ->
Logger.e("Caught : Exception ...")
}
coroutineScope.launch(handler) {
}
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>
My Android app crashes and I see this stack trace in Logcat. It doesn't tell me which line of code is causing the problem.
2021-05-05 09:13:33.143 1069-1069/com.mycompany.app E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.mycompany.app, PID: 1069
retrofit2.HttpException: HTTP 403
at retrofit2.KotlinExtensions$await$2$2.onResponse(KotlinExtensions.kt:53)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:161)
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)
Is there a way to map this back to my code, to see which call to retrofit is causing it? I have a repository with code like this:
suspend fun getSomeData(): Stuff {
return withContext(Dispatchers.IO) {
val body = myRetroApi.getStuff()
...
Do I need to wrap every withContext body to make sure no Throwables escape? I thought that if something threw an exception there, it would log an error, not crash the entire app.
Edit
I messed up when asking this question and put the emphasis on wrong things. So I'm removing the "retrofit" tag. It turns out the withContext(Dispatchers.IO) call does re-throw the Exception as expected, but when the exception gets back up to viewModelScope.launch, if that block does not catch it, the app crashes.
If the exception is not handled the app will crash of course.
You can add a try catch to avoid this:
suspend fun getSomeData() {
withContext(Dispatchers.IO) {
try{
val body = myRetroApi.getStuff()
...
} catch (e : Exception){
//your code
}
...
Retrofit is giving you a 403 Unauthorized HTTP exception. It may be that the server isn't passing any additional error message or that you need to catch HttpException and check for the message. In either case, this isn't a Retrofit issue hence it's just passing the error it's getting from the server you're calling.
It's best to create a network result wrapper and a wrapper function for API calls to handle exceptions.
You can do something like this. Keep in mind, the actual implementation is completely up to you. I would however suggest using runCatching when it comes to couroutines as it handles cancellation exceptions.
sealed class NetworkResult<out T> {
data class Success<T>(val data: T) : NetworkResult<T>()
data class Error(val exception: Throwable, val message: String?) : NetworkResult<Nothing>()
}
suspend fun networkCall(): String = ""
suspend fun <T> safeApiCall(block: suspend () -> T): NetworkResult<T> {
return runCatching {
withContext(Dispatchers.IO) {
block()
}
}.fold({
NetworkResult.Success(it)
}, {
when (it) {
is HttpException -> NetworkResult.Error(it, "Network error")
else -> NetworkResult.Error(it, "Some other message...")
// else -> throw it
}
})
}
suspend fun getData() {
val result: NetworkResult<String> = safeApiCall {
networkCall()
}
when (result) {
is NetworkResult.Success -> {
//Handle success
}
is NetworkResult.Error -> { //Handle error
}
}
}
runCatching uses Kotlin's built-in Result class and there are several ways of handling the result. These are just a few.
runCatching {
//.....
}.getOrElse { throwable ->
//handle exception
}
runCatching {
//.....
}.getOrThrow()
runCatching {
}.onSuccess {
}.onFailure {
}