error not bubbling up from observables in rxjava zip function - error-handling

I am trying to get my head around error handling in rxjava. I thought if i combine a stream of observables for instance in a zip() function that errors emitted by the observables within the zip would break the sequence and bubble up to the subscriber onError function. However the only error caught there are the ones emmitted in the BiFunction. Errors emitted up the chain causes the system to crash. when i add onErrorReturn to the observable and return a fallback value the system still crashes. So for me that does not work as I expected. What am I missing?
private fun getOneThing (): Single<String> {
println("getOneThing")
if (isOneBadCondition) {
throw Exception() //causes crash
} else {
return Single.just("a string thing")
}
}
private fun getAnotherThing(): Single<Boolean> {
println("getAnotherThing")
if (isAnotherBadCondition) {
throw Exception() //causes crash
} else {
return Single.just(true)
}
}
private fun createSomethingElse (): Int {
println("createAnother")
if (isBadCondition) {
throw Exception() //is handled onError
} else {
return 2
}
}
fun errorHandlingTest() {
Single.zip(
getOneThing(), //if I add onErrorReturn here it is not called after error
getAnotherThing(), //if I add onErrorReturn here it is not called after error
BiFunction<String, Boolean, Int> { t1, t2 ->
createSomethingElse()
}
).subscribeBy(
onSuccess ={ println(it) },
onError={ it.printStackTrace() }) //only error thrown by createSomethingElse() are caught here
}

Related

Getting data from Datastore for injection

I am trying to retrieve the base url from my proto datastore to be used to initialize my ktor client instance I know how to get the data from the datastore but I don't know how to block execution until that value is received so the client can be initialized with the base url
So my ktor client service asks for a NetworkURLS class which has a method to return the base url
Here is my property to retrieve terminalDetails from my proto datastore
val getTerminalDetails: Flow<TerminalDetails> = cxt.terminalDetails.data
.catch { e ->
if (e is IOException) {
Log.d("Error", e.message.toString())
emit(TerminalDetails.getDefaultInstance())
} else {
throw e
}
}
Normally when I want to get the values I would do something like this
private fun getTerminalDetailsFromStore() {
try {
viewModelScope.launch(Dispatchers.IO) {
localRepository.getTerminalDetails.collect {
_terminalDetails.value = it
}
}
} catch(e: Exception) {
Log.d("AdminSettingsViewModel Error", e.message.toString()) // TODO: Handle Error Properly
}
}
but in my current case what I am looking to do is return terminalDetails.backendHost from a function and that where the issue comes in I know I need to use a coroutine scope to retrieve the value so I don't need to suspend the function but how to a prevent the function returning until the coroutine scope has finished?
I have tried using async and runBlocking but async doesn't work the way I would think it would and runBlocking hangs the entire app
fun backendURL(): String = runBlocking {
var url: String = "localhost"
val job = CoroutineScope(Dispatchers.IO).async {
repo.getTerminalDetails.collect {
it.backendHost
}
}
url
}
Can anyone give me some assistance on getting this to work?
EDIT: Here is my temporary solution, I do not intend on keeping it this way, The issue with runBlocking{} turned out to be the Flow<T> does not finish so runBlocking{} continues to block the app.
fun backendURL(): String {
val details = MutableStateFlow<TerminalDetails>(TerminalDetails.getDefaultInstance())
val job = CoroutineScope(Dispatchers.IO).launch {
repo.getTerminalDetails.collect {
details.value = it
}
}
runBlocking {
delay(250L)
}
return details.value.backendHost
}
EDIT 2: I fully fixed my issue. I created a method with the same name as my val (personal decision) which utilizes runBlocking{} and Flow<T>.first() to block while the value is retrieve. The reason I did not replace my val with the function is there are places where I need the information as well where I can utilize coroutines properly where I am not initializing components on my app
val getTerminalDetails: Flow<TerminalDetails> = cxt.terminalDetails.data
.catch { e ->
if (e is IOException) {
Log.d("Error", e.message.toString())
emit(TerminalDetails.getDefaultInstance())
} else {
throw e
}
}
fun getTerminalDetails(): TerminalDetails = runBlocking {
cxt.terminalDetails.data.first()
}

How can I run Kotlin suspend fun on #ReactMethod (ReactNative)

I need to implement a custom gRPC on Kotlin native side.
#ReactMethod can't be suspend func.
How can I run it?
#ReactMethod
fun connect(ipAddress: String, port: Int) {
try {
channel = ManagedChannelBuilder.forAddress(ipAddress, port).usePlaintext().build()
var guidKey = Metadata.Key.of("GUID", Metadata.ASCII_STRING_MARSHALLER)
metadata.put(guidKey, GUID)
val stub = DBServiceGrpcKt.DBServiceCoroutineStub(channel!!)
var request = GrpcDBService.SignInRequest.newBuilder()
.setUserName("user")
.setPassword("11111")
.build()
try {
//******* this part *****
suspend fun coroutine() {
var response = stub.trySignIn(request,metadata)
}
} catch (e: Exception) {
Log.d("grpcConnect", e.localizedMessage)
}
} catch (e: Error) {
Log.d("grpcConnect ", e.localizedMessage)
}
finally {
channel?.shutdown()
}
}
You need to create a coroutine, for example, define a scope (somewhere in your class) and use it with launch:
myPluginScope.launch {
val response = stub.trySignIn(request,metadata)
// Return, the result
}
Creating the scope is easy, the tricky part is to find where to cancel it. Check the documentation on React native modules to find a good place to call cancel on your scope:
val myPluginScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
Now, is important to decide whether the connect function should behave as an asynchronous function or not. For example, if the sign in operation takes time or uses the network, connect should probably return the result through a Promise or Callback, so the Javascript side is not blocked:
#ReactMethod
fun connect(ipAddress: String, port: Int, promise: Promise) {
myPluginScope.launch {
try {
// Make the sign in happen in a separate thread:
val response = withContext(context = Dispatchers.IO) {
stub.trySignIn(request, metadata)
}
// Resolve the promise in the calling thread (The UI thread)
promise.resolve(response.hypotheticalCode)
} catch (e: Exception) {
promise.reject("Sign in error!", e)
}
}
}
See:
https://reactnative.dev/docs/native-modules-android#promises
https://kotlinlang.org/docs/async-programming.html#coroutines

How to get correct return value for suspend function when using GlobalScope.launch?

I have a suspend function
private suspend fun getResponse(record: String): HashMap<String, String> {}
When I call it in my main function I'm doing this, but the type of response is Job, not HashMap, how can I get the correct return type?
override fun handleRequest(event: SQSEvent?, context: Context?): Void? {
event?.records?.forEach {
try {
val response: Job = GlobalScope.launch {
getResponse(it.body)
}
} catch (ex: Exception) {
logger.error("error message")
}
}
return null
}
Given your answers in the comments, it looks like you're not looking for concurrency here. The best course of action would then be to just make getRequest() a regular function instead of a suspend one.
Assuming you can't change this, you need to call a suspend function from a regular one. To do so, you have several options depending on your use case:
block the current thread while you do your async stuff
make handleRequest a suspend function
make handleRequest take a CoroutineScope to start coroutines with some lifecycle controlled externally, but that means handleRequest will return immediately and the caller has to deal with the running coroutines (please don't use GlobalScope for this, it's a delicate API)
Option 2 and 3 are provided for completeness, but most likely in your context these won't work for you. So you have to block the current thread while handleRequest is running, and you can do that using runBlocking:
override fun handleRequest(event: SQSEvent?, context: Context?): Void? {
runBlocking {
// do your stuff
}
return null
}
Now what to do inside runBlocking depends on what you want to achieve.
if you want to process elements sequentially, simply call getResponse directly inside the loop:
override fun handleRequest(event: SQSEvent?, context: Context?): Void? {
runBlocking {
event?.records?.forEach {
try {
val response = getResponse(it.body)
// do something with the response
} catch (ex: Exception) {
logger.error("error message")
}
}
}
return null
}
If you want to process elements concurrently, but independently, you can use launch and put both getResponse() and the code using the response inside the launch:
override fun handleRequest(event: SQSEvent?, context: Context?): Void? {
runBlocking {
event?.records?.forEach {
launch { // coroutine scope provided by runBlocking
try {
val response = getResponse(it.body)
// do something with the response
} catch (ex: Exception) {
logger.error("error message")
}
}
}
}
return null
}
If you want to get the responses concurrently, but process all responses only when they're all done, you can use map + async:
override fun handleRequest(event: SQSEvent?, context: Context?): Void? {
runBlocking {
val responses = event?.records?.mapNotNull {
async { // coroutine scope provided by runBlocking
try {
getResponse(it.body)
} catch (ex: Exception) {
logger.error("error message")
null // if you want to still handle other responses
// you could also throw an exception otherwise
}
}
}.map { it.await() }
// do something with all responses
}
return null
}
You can use GlobalScope.async() instead of launch() - it returns Deferred, which is a future/promise object. You can then call await() on it to get a result of getResponse().
Just make sure not to do something like: async().await() - it wouldn't make any sense, because it would still run synchronously. If you need to run getResponse() on all event.records in parallel, then you can first go in loop and collect all deffered objects and then await on all of them.

RxJava's retryWhen unexpectedly keeps retrying forever

Can anyone …
explain why the following code keeps retrying indefinitely, once retryWhen is called?
correct the code to stop retrying when Random.nextBoolean()
returns true?
I think there is a 50% chance that retryWhen is called because of Random.nextBoolean(). However, once it is called, it goes into an infinite loop.
import io.reactivex.rxjava3.core.Observable
import kotlin.random.Random
fun main() {
Observable
.just(Unit)
.flatMap {
if (Random.nextBoolean()) {
Observable.just(Unit)
} else {
Observable.error(Throwable())
}.retryWhen {
it.flatMap {
println("retryWhen")
Observable.just(Unit)
}
}
}
.subscribe()
}
Output:
retryWhen
retryWhen
retryWhen
⋮
(continued)
You've applied the retryWhen to the result of the execution of either branch of the if-statement.
Chain the retryWhen operator to the stream produced by flatMap:
e.g.
Observable
.just(Unit)
.flatMap {
if(Random.nextBoolean()) {
Observable.just(Unit)
} else {
Observable.error(Throwable())
}
}
.retryWhen {
it.flatMap {
println("retryWhen")
Observable.just(Unit)
}
}
.subscribe()
Please take a look the same question and answer
Copied answer:
retryWhen calls the provided function when an Observer subscribes to it so you have a main sequence accompanied by a sequence that emits the Throwable the main sequence failed with. You should compose a logic onto the Observable you get in this Function so at the end, one Throwable will result in a value on the other end.
I think your expectation is:
Observable
.just(Unit)
.flatMap {
if (Random.nextBoolean()) {
Observable.just(Unit)
} else {
Observable.error(Throwable())
}.retryWhen {
it.takeWhile {
Random.nextBoolean()
}
.doOnNext {
println("retryWhen")
}
}
}
.subscribe()

RxAndroid - Handle Errors with Zip operator

I'm trying to find a way to execute requests in parallel and handle them when every observable finishes. Despite everything is working when all observables gives a response, I not seeing a way to handle each all errors when everything is finished.
This is a sample of zip operator, which basically executes 2 requests in parallel:
Observable.zip(
getObservable1()
.onErrorResumeNext { errorThrowable: Throwable ->
Observable.error(ErrorEntity(Type.ONE, errorThrowable))
}.subscribeOn(Schedulers.io()),
getObservable2()
.onErrorResumeNext { errorThrowable: Throwable ->
Observable.error(ErrorEntity(Type.TWO, errorThrowable))
}.subscribeOn(Schedulers.io()),
BiFunction { value1: String, value2: String ->
return#BiFunction value1 + value2
})
//execute requests should be on io() thread
.subscribeOn(Schedulers.io())
//there are other tasks inside subscriber that need io() thread
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result ->
Snackbar.make(view, "Replace with your own action " + result, Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
},
{ error ->
Log.d("TAG", "Error is : " + (error as ErrorEntity).error.message)
}
)
private fun getObservable1(): Observable<String> {
return Observable.defer {
throw Throwable("Error 1")
}
}
private fun getObservable2(): Observable<String> {
return Observable.defer {
throw Throwable("Error 2")
}
}
Problem with this approach is that there is no mechanism to join each error like BiFunction do for the success case. Therefore, the zip operator will only trigger the first error and will ignore the others.
Output:
D/TAG: Error is : Error 1
Is there any way to retrieve all errors only after every observable inside zip completed or gave an error?
My main goal is to see which requests gave an error and execute only those after a dialog appears to the user asking him if he wants to retry the failed requests.
You can model your observables using data classes. E.g.
sealed class Response {
data class Success(val data: String) : Response()
data class Error(val t: Throwable) : Response()
}
then you can map your observables to Response like this:
val first: Observable<Response> = observable1
.map<Response> { Response.Success(it) }
.onErrorReturn { Response.Error(it) }
val second: Observable<Response> = observable2
.map<Response> { Response.Success(it) }
.onErrorReturn { Response.Error(it) }
and you can combine them:
Observable.zip(
first,
second,
BiFunction { t1: Response, t2: Response -> Pair(t1, t2) }
).subscribe({println(it)})
this prints:
(Error(t=java.lang.Exception: Error 1), Error(t=java.lang.Exception:
Error 2))
Also take a look at this article.