Send upstream exception in SharedFlow to collectors - kotlin

I want to achieve the following flow logic in Kotlin (Android):
Collectors listen to a List<Data> across several screens of my app.
The source-of-truth is a database, that exposes data and all changes to it as a flow.
On the first initialization the data should be initialized or updated via a remote API
If any API exception occurs, the collectors must be made aware of it
In my first attempt, the flow was of the type Flow<List<Data>>, with the following logic:
val dataFlow = combine(localDataSource.dataFlow, flow {
emit(emptyList()) //do not wait for API on first combination
emit(remoteDataSource.suspendGetDataMightThrow())
}) { (local, remote) ->
remote.takeUnless { it.isEmpty() }?.let { localDataSource.updateIfChanged(it) }
local
}.shareIn(externalScope, SharingStarted.Lazily, 1)
This worked fine, except when suspendGetDataMightThrow() throws an exception. Because shareIn stops propagating the exception through the flow, and instead breaks execution of the externalScope, my collectors are not notified about the exception.
My solution was to wrap the data with a Result<>, resulting of a flow type of Flow<Result<List<Data>>>, and the code:
val dataFlow = combine(localDataSource.dataFlow, flow {
emit(Result.success(emptyList())) //do not wait for API on first combination
emit(runCatching { remoteDataSource.suspendGetDataMightThrow() })
}) { (local, remote) ->
remote.onSuccess {
data -> data.takeUnless { it.isEmpty() }?.let { localDataSource.updateIfChanged(it) }
}
if (remote.isFailure) remote else local
}.shareIn(externalScope, SharingStarted.Lazily, 1)
I can now collect it as follows, and the exception is passed to the collectors:
dataRepository.dataFlow
.map { it.getOrThrow() }
.catch {
// ...
}
.collect {
// ...
}
Is there a less verbose solution to obtain the exception, than to wrap the whole thing in a Result?
I am aware that there are other issues with the code (1 API failure is emitted forever). This is only a proof-of-concept to get the error-handling working.

Related

Why is the value not entering the list?

At 'urichecking2' log, I can see there is value. But in 'uriChecking' the uriList is null.
why the uriList.add not work??
private fun getPhotoList() {
val fileName = intent.getStringExtra("fileName")
Log.d("fileNameChecking", "$fileName")
val listRef = FirebaseStorage.getInstance().reference.child("image").child(fileName!!)
var tmpUrl:Uri = Uri.parse(fileName)
Log.d("firstTmpUri","$tmpUrl")
listRef.listAll()
.addOnSuccessListener { listResult ->
for (item in listResult.items) {
item.downloadUrl.addOnCompleteListener { task ->
if (task.isSuccessful) {
tmpUrl = task.result
Log.d("secondTmpUri","$tmpUrl")
Log.d("urichecking2","$task.result")
uriList.add(task.result)
} else {
}
}.addOnFailureListener {
// Uh-oh, an error occurred!
}
}
}
Log.d("thirdTmpUri","$tmpUrl")
Log.d("urichecking", "$uriList")
}
If I do this, the log is output in the order of first, third, and second, and the desired value is in second, but when third comes out, it returns to the value of first.
The listAll method (like most cloud APIs these days, including downloadUrl which you also use) is asynchronous, since it needs to make a call to the server - which may take time. This means the code executes in a different order than you may expect, which is easiest to see if you add some logging:
Log.d("Firebase","Before starting listAll")
listRef.listAll()
.addOnSuccessListener { listResult ->
Log.d("Firebase","Got listResult")
}
Log.d("Firebase","After starting listAll")
When you run this code it outputs:
Before starting listAll
After starting listAll
Got listResult
This is probably not the order you expected, but it perfectly explains why you can't see the list result. By the time your Log.d("urichecking", "$uriList") runs, none of the uriList.add(task.result) has been called yet.
The solution for this is always the same: any code that needs the list result, has to be inside the addOnCompleteListener callback, be called from there, or be otherwise synchronized.
So in its simplest way:
listRef.listAll()
.addOnSuccessListener { listResult ->
for (item in listResult.items) {
item.downloadUrl.addOnCompleteListener { task ->
if (task.isSuccessful) {
uriList.add(task.result)
Log.d("urichecking", "$uriList")
}
}
}
}
This is an incredibly common mistake to make if you're new to programming with asynchronous APIs, so I recommend checking out
Asynchronous programming techniques in the Kotlin language guide
How to get URL from Firebase Storage getDownloadURL
Can someone help me with logic of the firebase on success listener
Why does my function that calls an API or launches a coroutine return an empty or null value?

kotlin flow using flatmap cannot call method in collect

lifecycleScope.launch {
adapter?.getData()?.let {
val flowable = it.asFlow()
flowable.onEach {
doCompress(it)
}.flatMapConcat {
flow<Unit> {
updateProgressInMain()
}.flowOn(Dispachers.Main)
}.catch {
dismissLoading()
}.flowOn(Dispatchers.IO).collect {
Log.d("Collect", "" + Thread.currentThread())
}
}
}
As above code, I cannot print 'Collect' log in console but other code can run well. However, I can print the log when I use 'WithContext()' in onEach period instead of flatMapConcat to switch Thread. Could anyone discribe what happened?
You produce an empty Flow that never emits in flatMapConcat, so the resulting Flow will never emit anything either.
Your code doesn't quite make sense to me, but supposing the task you want to do is, for each item emitted by the source LiveData as Flow:
Pass it to doCompress() on the IO Dispatcher. Apparently doCompress() doesn't return anything.
Call updateProgressInMain() on the main thread after eeach item is compressed.
And then call dismissLoading() whether or not it failed.
Then this simpler code should do it:
adapter?.getData()?.asFlow()?.onEach {
runCatching {
withContext(Dispatchers.IO) {
doCompress(it)
Log.d("Collect", "" + Thread.currentThread())
}
updateProgressInMain()
}
dismissLoading()
}?.launchIn(lifecycleScope)

How to make several synchronuous call of rxjava Single

I have difficulties making sequential calls of RxJava Single observerable. What I mean is that I have a function that makes http request using retrofit that returns a Single.
fun loadFriends(): Single<List<Friend>> {
Log.d("msg" , "make http request")
return webService.getFriends()
}
and if I subscribe from several places at the same time:
loadFriends().subscribeOn(Schedulers.io()).subscribe()
loadFriends().subscribeOn(Schedulers.io()).subscribe()
I want that loadFriends() makes only one https request but in this case I have two http request
I know how to solve this problem in blocking way:
The solution is to make loadFriends() blocking.
private val lock = Object()
prival var inMemoryCache: List<Friends>? = null
fun loadFriends(): Single<List<Friend>> {
return Single.fromCallable {
if(inMemoryCache == null) {
synchronize(lock) {
if(inMemoryCache == null) {
inMemoryCache = webService.getFriends().blockingGet()
}
}
}
inMemoryCache
}
But I want to solve this problem in a reactive way
You can remedy this by creating one common source for all your consumers to subscribe to, and that source will have the cache() operator invoked against it. The effect of this operator is that the first subscriber's subscription will be delegated downstream (i.e. the network request will be invoked), and subsequent subscribers will see internally cached results produced as a result of that first subscription.
This might look something like this:
class Friends {
private val friendsSource by lazy { webService.getFriends().cache() }
fun someFunction() {
// 1st subscription - friends will be fetched from network
friendsSource
.subscribeOn(Schedulers.io())
.subscribe()
// 2nd subscription - friends will be fetched from internal cache
friendsSource
.subscribeOn(Schedulers.io())
.subscribe()
}
}
Note that the cache is indefinite, so if periodically refreshing the list of friends is important you'll need to come up with a way to do so.

What is the difference between await functions (coroutine) and subscribe on Reactor

I'm using Kotlin + Reactor (Mono and Flux) and I wanna know the difference between using await...() (from kotlin-coroutines-reactive) function and subscribe() (from Reactor). I brought two examples to show what I'm trying to do.
Example 1 (with await function):
#Test
internal fun test() = runBlockingTest {
Mono.error<String>(IllegalStateException("exception"))
.doOnError {
print("error")
}.awaitFirst().let {
print("success")
}
}
Output: "error" with the IllegalStateException stack trace.
Example 2 (with subscribe function):
#Test
internal fun test() = runBlockingTest {
Mono.error<String>(IllegalStateException("exception"))
.doOnError {
print("error")
}.subscribe {
print("success")
}
}
Output:
Just "error".
Why example 1 shows the stack trace and example 2 doesn't show?
Thanks.
When you call subscribe on a reactive chain you decouple it from the main flow, it becomes independent and potentially asynchronous. Error is travelling on the reactive stream as a signal rather than as a traditionally thrown exception. In this case error handling is the responsibility of the reactive chain using operators like doOnError, onErrorMap, onErrorReturn, etc.
On the other hand Kotlin's await breaks this independence and attaches the reactive stream back to the main flow and lets you write reactive/asynchronous code as it would be imperative (e.g.: try-catch blocks, unwrapped function return types, etc.).

Post API using Ratpack and Groovy giving 405 Error and RxJava methods not working

I am building an API using Ratpack and Groovy. The POST API is always giving:
405-Method not Found Error
This is a snippet from POST Endpoint Handler. In this code, promiseSingle, then, observe, map, doOnNext, doOnError, etc.
RxJAVA functions are not working. Is there any reason why RxJava methods are not working?
saveJsonAsData(context, id)
.promiseSingle()
.then { Data updateddata ->
context.response.headers
.add(HttpHeaderNames.LOCATION, "/api/save/${updateddata.id}/${updateddata.value}")
context.response.status(HttpResponseStatus.CREATED.code())
.send()
}
}
protected Observable<Data> saveJsonAsData(GroovyContext context, String id) {
context.request.body.observe()
.map { TypedData typedData -> extractData(context, typedData) }
.doOnNext { Data data ->
data.id = id
validatorWrapper.validate(data)
}
.flatMap(data.&save as Func1)
.doOnError { Throwable throwable -> log.error("Error saving data", throwable) }
}
The issue is not so much with Rx as it is with the usage of the Context.
You should try to keep the response handling logic within your Handler, that is don't pass the Context around, rather get the objects you need and pass them to your services.
As an example
path('myendpoint') { MyRxService service ->
byMethod {
get {
// do something when request is GET
}
post {
request.body.map { typedData ->
extractItem(typeData) // extract your item from the request first
}.flatMap { item ->
service.saveJsonAsItemLocation(item).promiseSingle() // then once it's extracted pass the item to your "saveJsonAsItemLocation" method
}.then { ItemLocationStore updatedItem ->
response.headers.add(HttpHeaderNames.LOCATION, "/itemloc/v1/save/${updatedItem.tcin}/${updatedItem.store}")
context.response.status(HttpResponseStatus.CREATED.code()).send()
}
}
}
}
My guess is that you have something like this:
get {
// get stuff
}
post {
// post stuff
}
The reason this doesn't work is that Ratpack doesn't use Routing Table for handling incoming requests, instead it uses chain delegation. The get {} binds to root path and GET http method and post {} binds to root path and POST http method. Because get {} matches the path, Ratpack considers the handler matched and since the handler is for GET it considers it a 405.
There are chain methods available that binds regardless of HTTP Method such as all {} and path {}. Chain#all will handle all paths and methods where as Chain#path(String) matches against specific path.
Hope this helps.