Catching exceptions in Kotlin Flows with .catch - android

I'm a bit confused on how catching exceptions exactly work in Kotlin flows.
Looking at this sample code from https://developer.android.com/kotlin/flow#exceptions.
class NewsRepository(...) {
val favoriteLatestNews: Flow<List<ArticleHeadline>> =
newsRemoteDataSource.latestNews
.map { news -> news.filter { userData.isFavoriteTopic(it) } }
.onEach { news -> saveInCache(news) }
// If an error happens, emit the last cached values
.catch { exception -> emit(lastCachedNews()) }
Does .catch catch any exceptions thrown by the following?
newsRemoteDataSource.latestNews
.map
.onEach
If an exception was caught from .map for example, does .onEach ever get run? Or does it jump straight to the .catch?

Does .catch catch any exceptions thrown by the following?
newsRemoteDataSource.latestNews
map
onEach
catch catches exception thrown from any of these, and it would cancel the flow as soon as it catches any exception.
If an exception was caught from .map for example, does .onEach ever get run? Or does it jump straight to the .catch?
Order of execution is from left to right, so in this case, if an exception occurs in map it would instantly catch the exception and cancel the flow, and it won't run onEach. If you move onEach before map, it would do the vice versa.
If any exception occurs in newsRemoteDataSource.latestNews, it won't run either of map and onEach.

Related

Kotlin flow emits nothing when I call the function second time

I try to implement deleting user in FirebaseAuth using Kotlin flow (SharedFlow).
In onDeleteAccountClicked() there is delete() method called from FirebaseAuth which may throw AuthReauthenticationRequiredException. When the exception is thrown, app redirects to another fragment to reauthenticate, then call onDeleteAccountClicked() once again, but flow emits nothing.
ViewModel
private val _deleteAccount = MutableSharedFlow<() -> Unit>()
fun onDeleteAccountClicked() {
logd("outside the viewModelScope")
viewModelScope.launch {
logd("inside the viewModelScope")
_deleteAccount.emit {
logd("emitting log")
firebaseAuth.deleteUser()
//throw AuthReauthenticationRequiredException()
}
}
}
init {
viewModelScope.launch {
_deleteAccount
.onEach {
it()
}
.catch {
if (it is AuthReauthenticationRequiredException) {
_redirectToSignInMethodsScreen.emit(Unit)
}
}
.collect()
}
}
Logs "outside the viewModelScope" and "inside the viewModelScope" shows every time when the method is called, but "emitting log" only for the first time.
Am I even trying to do it the right way?
I just tested the code, and it works for me. I called onDeleteAccountClicked() three times with delay between calling, and all three logs "emitting log" inside emit lambda were printed. Try to remove calling firebaseAuth.deleteUser() inside emit lambda and test. Calling FirebaseUser.delete function when user is already deleted throws FirebaseAuthInvalidUserException exception. Maybe that's why you didn't see logs - because FirebaseUser.delete function throws an exception.
I think the structure you use for calling just one function is a bit complicated, I can suggest to get rid of _deleteAccount flow and just wrap firebaseAuth.deleteUser() inside try-catch (you even don't need to launch a coroutine for that):
fun onDeleteAccountClicked() {
try {
firebaseAuth.deleteUser()
} catch(e: AuthReauthenticationRequiredException) {
_redirectToSignInMethodsScreen.emit(Unit)
}
}

Kotlin coroutine Job's join method

I have found very strange documentation for join method:
In particular, it means that a parent coroutine invoking join on a
child coroutine that was started using launch(coroutineContext) { ...
} builder throws CancellationException if the child had crashed,
unless a non-standard CoroutineExceptionHandler is installed in the
context.
I'm not sure that CoroutineExceptionHandler will have effect for CancellationException.
Example:
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler got $exception")
}
val job = GlobalScope.launch(handler) {
val inner = launch { // all this stack of coroutines will get cancelled
throw IOException() // the original exception
}
try {
inner.join()
} catch (e: CancellationException) {
println("handle join")
}
}
job.join()
}
Output:
handle
join CoroutineExceptionHandler got java.io.IOException
So basically CancellationException will still be thrown regardless any installed handlers.
Am I right?
Yes. Your innerJob will still throw a CancellationException, and in your outer job you'll get a crash, since you don't handle the exception.
See: https://pl.kotl.in/1Uqw8nmNS

JobCancellationException with in-app updates

I've noticed that some of the users have issues to use flexible in-app update, the JobCancellationException: Job was cancelled is thrown with incomprehensible stack trace:
at dalvik.system.VMStack.getThreadStackTrace(VMStack.java)
at java.lang.Thread.getStackTrace(Thread.java:1538)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1068)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1063)
at java.lang.Thread.dispatchUncaughtException(Thread.java:1955)
Unfortunately, I don't which part of the code is causing the issue. This is the only coroutine related code, staying in MyViewModel:
init {
viewModelScope.launch {
try {
appUpdateManager.requestUpdateFlow().collect { appUpdateResult ->
// Do something with result.
}
} catch (e: InstallException) {
// Do something with an error.
}
}
}
fun requestUpdate(fragment: Fragment) {
viewModelScope.launch {
try {
val appUpdateInfo = appUpdateManager.requestAppUpdateInfo()
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.FLEXIBLE,
fragment,
REQUEST_CODE
)
} catch (e: IntentSender.SendIntentException) {
}
}
}
I suspect that code inside requestUpdateFlow() is calling offer while the coroutine job is already cancelled and I can't see the exact stacktrace, because Play Core library is obfuscated?
I'm using following versions of the libraries:
"com.google.android.play:core:1.7.2"
"com.google.android.play:core-ktx:1.7.0"
JobCancellationException: Job was cancelled is thrown in almost case is job in coroutine scope is cancelled.
Example: User go to a screen a in which call api to get something. But user press back to close this screen while api not complete. Thus, when receive response, job cancelled before -> exception.
To more handle JobCancellationException you can using suspendCancellableCoroutine.
More detail : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/suspend-cancellable-coroutine.html

PublishSubject onError function in Kotlin

So, I am getting an error in my Android app (Kotlin) when trying to subscribe to a PublishSubject.
The error explanation is pretty straight forward, however, I have failed trying to implement this, onError function and I am not sure how to do it in a god way.
Here the error
The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | com.androidnetworking.error.ANError
Here the PublishSubject:
var positionSubject = PublishSubject.create<Location>()
Here when I subscribe (which gives error inside the code of the subscription):
compositeDisposable.add(
positionSubject.subscribe {
// do some actions here that causes Exception
}
)
Here my attempt to fix it in a "nice" way (did not work, still crashes in subscribe):
compositeDisposable.add(
positionSubject
.onErrorReturn { t ->
Log.d("debug", "EXCEPTION OCCURRED")
Location("")}
.subscribe {
// do some actions here that causes Exception
}
)
Here what I ended up doing to fix it and not crashing:
compositeDisposable.add(
positionSubject.subscribe {
try{
// do some actions here that causes Exception
}catch(e:Exception){
Log.d("debug", "EXCEPTION OCCURRED $e")
}
}
)
I am wondering how to this in a cleaner way than using the try/catch block inside the subscribe, if it is even possible.
Following code is kotlin way to subscribe a PublishSubject
var positionSubject = PublishSubject.create<Location>()
positionSubject.subscribe({ location ->
}, { error ->
})
This should work fine.

Rx Exception Handling Fail

I'm using RxJava with a retrofit to make API calls,
By using RxJava methods like flatMap and map I'm making API calls as well as performing DB operations in room database on the background thread.
My implementation is perfect and working fine if there is no error, but In the case when I got an error or any exception while performing DB Operation, Application getting crashed due to following Rx error.
E/AndroidRuntime: FATAL EXCEPTION: RxCachedThreadScheduler-1
The exception was not handled due to missing onError handler in the subscribe() method call.
I have used RxJava to perform my operation as below :
mDataManager.login(params)
.flatMap { loginResponse: LoginResponse ->
// here making another API call based on previos API result
return#flatMap mDatamanager....
}
.flatMap { object: SomeDataModel ->
// here inserting data to DB
mDataManager.insertDataToDB(object).subscribe()
// here making another API call based on previos API
return#flatMap mDataManager...
}.map {
// here inserting data to DB
mDataManager.insertDataToDB(object).subscribe()
return#map true
}
.observeOn(mSchedulerProvider.ui())
.subscribeOn(mSchedulerProvider.io())
.subscribe({ result ->
// updating view
}, { throwable ->
throwable.printStackTrace()
})
I'm getting an exception while inserting data to DB
Exception java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase:
But the error not handled and Application getting crash.
The error says that missing onError handler in the subscribe() method but in my code, I already override throwable to handle exception/errors.
Can anyone find, what I'm missing or what mistake I have done in code.
UPDATE
Found the solution, Mistake was here :
mDataManager.insertDataToDB(object).subscribe()
In the Above statement, I'm subscribing but was not handling the error for that and because of that error was not handled by rxJava and in the result, the application gets crashed.
Final Code as below :
mDataManager.login(params)
.flatMap { loginResponse: LoginResponse ->
// here making another API call based on previos API result
return#flatMap mDatamanager....
}
.flatMap { object: SomeDataModel ->
// avoid this inner subscribe
// mDataManager.insertDataToDB(object).subscribe()
return#flatMap mDataManager.insertDataToDB(object)
}
.flatMap {
// here making another API call based on previos API result
return#flatMap mDatamanager....
}
.flatMap {
// avoid this inner subscribe
//mDataManager.insertDataToDB(object).subscribe()
return#flatMap mDataManager.insertDataToDB(object)
}
.observeOn(mSchedulerProvider.ui())
.subscribeOn(mSchedulerProvider.io())
.subscribe({ result ->
// updating view
}, { throwable ->
throwable.printStackTrace()
})
Above code is working Fine!
The reason is you are subscribing here
.map {
// here inserting data to DB
mDataManager.insertDataToDB(object).subscribe()
return#map true
}
And this subscribe is not handling the error scenario.
I feel it's not a good practice to call subscribe() for the inner streams. In your scenario the flow is broken in-between.
The best way according to me is instead of using map and calling subscribe() here, use,
flatMap{
object -> mDataManager.insertDataToDB(object)
}
This way, if any error, it will be caught in last outer subscribe().
Hope this answer helps.

Categories

Resources