Im developing an app using kotlin and MVVM architecture.I have the three layers activity,viewModel and repository, in repository i have renameDirectory() function it does some network calling to rename a directory, the function can throw an exception if the network response returns an error the problem is that the catch block in the activity layer does not catch the exception.
renameDirectory in repository
suspend fun renameDirectory(token : String,directory: Directory) {
val resp = maApi.renameDirectory("jwt $token",directory)
if(resp.isSuccessful)
return
val gson = Gson()
val type = object : TypeToken<ErrorResponse>() {}.type
val errorResponse =
gson.fromJson<ErrorResponse>(resp.errorBody()!!.charStream(), type)
throw Exception(errorResponse.error)
}
code in viewModel that calls the function
suspend fun renameDirectory(directory: Directory){
viewModelScope.launch (Dispatchers.IO){
directoriesRepository.renameDirectory(userResp!!.token!!,directory)
}
}
code in activity to calls the function and handle exceptions
try {
viewModel.renameDirectory(directory)
withContext(Dispatchers.Main) {
horizontalProgress.toggle()
activityView.snackBar("Directory has been renamed successfully")
currentFragment.clearSelection()
}
} catch (ex: IOException) {
Log.d("IO Exception=>", ex.toString())
} catch (ex: HttpException) {
Log.d("Http Exception=>", ex.message())
} catch (ex: Exception) {
this.cancel()
withContext(Dispatchers.Main) {
horizontalProgress.toggle()
activityView.snackBar(ex.message!!)
}
}
when renameDirectory in repository calls throw Exception() the app stops,why the code in activity does not handle the exception?
Related
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?
I'm using the code below for a network request throught okhttp3:
runOnDefaultDispatcher {
try {
val newsResponse = Xxxx.xxxxClient.getNews()
if (newsResponse.success && newsResponse.data != null && newsResponse.data.count() > 0) {
runOnMainDispatcher {
val adapter = NewsAdapter(newsResponse.data, getString(R.string.news)).also {
initListener(it, newsResponse.data)
}
binding.list.adapter = adapter
adapter.notifyDataSetChanged()
}
}
} catch (exception: Exception) {
runOnMainDispatcher {
binding.list.visibility = View.GONE
val errorLayout = view.findViewById<RelativeLayout>(R.id.error_layout)
errorLayout.visibility = View.VISIBLE
errorLayout.findViewById<TextView>(R.id.error_title).text = "Oops..."
errorLayout.findViewById<TextView>(R.id.error_message).text = exception.message
}
}
}
The implementation code of runOnDefaultDispatcher and runOnMainDispatcher is down below:
import kotlinx.coroutines.*
fun block(block: suspend CoroutineScope.() -> Unit): suspend CoroutineScope.() -> Unit {
return block
}
fun runOnDefaultDispatcher(block: suspend CoroutineScope.() -> Unit) =
GlobalScope.launch(Dispatchers.Default, block = block)
suspend fun <T> onDefaultDispatcher(block: suspend CoroutineScope.() -> T) =
withContext(Dispatchers.Default, block = block)
fun runOnIoDispatcher(block: suspend CoroutineScope.() -> Unit) =
GlobalScope.launch(Dispatchers.IO, block = block)
suspend fun <T> onIoDispatcher(block: suspend CoroutineScope.() -> T) =
withContext(Dispatchers.IO, block = block)
fun runOnMainDispatcher(block: suspend CoroutineScope.() -> Unit) =
GlobalScope.launch(Dispatchers.Main.immediate, block = block)
suspend fun <T> onMainDispatcher(block: suspend CoroutineScope.() -> T) =
withContext(Dispatchers.Main.immediate, block = block)
I except the exception would be caught and no crash would appear.
However the application still CRASH:
FATAL EXCEPTION: DefaultDispatcher-worker-2
Java.net.SocketException: Connection reset
The calls to launch don't work well with try/catch.
e.g. this will crash the app
try {
GlobalScope.launch { throw Excepton() }
} catch (e: Exception) {
}
On the other hand, suspend functions work with try/catch as you would expect so this example DOES NOT crash the app:
suspend fun bang(): Unit = throw Exception()
try {
bang()
} catch (e: Exception) {
}
In your code you have launch inside try/catch, meaning you have a scenario like the first example here.
The solution is to build your program as suspend functions, and only use launch one to execute the result (note: this doesn't apply universally but does apply in this scenario).
When running the program you probably want to use lifecycleScope.
Also you might want to consider using a ViewModel so that the network call survives configuration changes.
You can check the Kotlin Coroutines on Android guide for more.
You can see the difference between to code blocks. The first one will crash because exception occurs in a different thread. The second one will not crash because you are catching exception in the right place.
fun main() {
val scope = CoroutineScope(Job() + Dispatchers.Default)
//First way
try {
scope.launch {
exceptionThrowingFunction()
}
} catch (e: Exception) {
println(e.message)
}
//Second way
scope.launch {
try {
exceptionThrowingFunction()
} catch (e: Exception) {
println(e.message)
}
}
Thread.sleep(10000)
}
private suspend fun exceptionThrowingFunction() {
delay(10)
throw IllegalArgumentException("Test Error")
}
I have the following setup
Service
// ItunesService
suspend fun searchItunesPodcast(#Query("term") term: String): Response<PodcastResponse>
Repository
// ItunesRepo
override suspend fun searchByTerm(term: String) = withContext(ioDispatcher) {
return#withContext itunesService.searchItunesPodcast(term)
}
ViewModel
fun searchPodcasts(term: String) {
viewModelScope.launch {
_res.value = Result.loading()
try {
val response = itunesRepo.searchByTerm(term)
if (response.isSuccessful) { // Nothing from here when no internet
_res.value = Result.success(response.body())
} else {
_res.value = Result.error(response.errorBody().toString())
}
} catch (e: Exception) {
_res.value = Result.exception(e)
}
}
}
Everything works great until i turn off mobile data/internet on my testing device. _res value stuck on Loading state. I have tried adding break point at if (response.isSuccessful) when there is no internet and it seams like val response = itunesRepo.searchByTerm(term) never returns how can I fix this
I switched to using Flow api on my Repository
override suspend fun searchPodcasts(term: String) = flow {
emit(Result.Loading)
try {
val res = itunesService.searchItunesPodcast(term)
if (res.isSuccessful)
emit(Result.Success(res.body()))
else
emit(Result.Error("Generic error: ${res.code()}"))
} catch (e: Exception) {
emit(Result.Error("Unexpected error", e))
}
}.flowOn(ioDispatcher)
Then collect the results on my ViewModels
I'm new to coroutines and having a hard time figuring out how to correctly wrap an existing callback in a coroutine.
My goal is to be able to do the following:
lifecycleScope.launch {
withContext(Dispatchers.Main) {
val theResult = getPreRollAd() //1. call this suspending func and wait for result
doSomethingWithResult(theResult) //2. now that the result is returned from AdsWizz API (below), do something with it
}
}
Here is the AdsWizz API call that I'd like to "wrap":
val adReqInterface: AdRequestHandlerInterface = object :
AdRequestHandlerInterface {
override fun onResponseError(error: AdswizzSDKError) {
Timber.e("onResponseError $error")
}
override fun onResponseReady(adResponse: AdResponse) {
Timber.d( "onResponseReadySingleAd")
//this contains the url to the ad, title, etc..
!!!*** I WANT TO RETURN THE adResponse.mediaFile?.source string back to "theResult" variable above (in lifecycleScope.launch {.... )
}
}
try {
AdswizzSDK.getAdsLoader().requestAd(adReqParams, adReqInterface)
} catch (e: IllegalArgumentException) {
Timber.d( "IllegalArgumentException")
} catch (e: SecurityException) {
Timber.d( "SecurityException")
} catch (e: Exception) {
Timber.d( "other exception")
e.printStackTrace()
}
I've tried using suspendCoroutine {... to wrap but nothing is working. Really appreciate someones help re the right way to achieve this.
the right way to do it is to use suspendCancellableCoroutine. It can return a result or can be cancelled with an exception.
suspend fun getPreRollAd(): AdResponse {
return suspendCancellableCoroutine {
...
val adReqInterface: AdRequestHandlerInterface = object : AdRequestHandlerInterface {
override fun onResponseError(error: AdswizzSDKError) {
Timber.e("onResponseError $error")
it.cancel(error)
}
override fun onResponseReady(adResponse: AdResponse) {
Timber.d( "onResponseReadySingleAd")
it.resume(adResponse)
}
}
AdswizzSDK.getAdsLoader().requestAd(adReqParams, adReqInterface)
}
}
viewModelScope.launch {
val result = try {
getPreRollAd()
} catch(e: Throwable) {
null
}
...
}
following the code, in init function I create a Person object,and have an exception,now I want to stop the progress in catch like java return. How can I do it?
class Person {
val age: String = "10"
private lateinit var person: Person
init {
try {
person = get(2)
} catch (exception: Throwable) {
}
println("----------------do it $person.age")
}
fun get(i: Int): Person {
when (i) {
1 -> {
return Person()
}
else -> {
throw MyException("aaaaaaaaa")
}
}
}
}
If an instance cannot be created due to errors in init, this error shouldn't be suppressed but delegated to the caller. So just do not catch the exception and the init "stopps" automatically.