I have a simple stream like that:
Observable.error<Int>(Exception()).startWith(1).subscribe {
println("Item is $it")
}
Everything is working like expected. First onNext is called with integer 1 and then exception is thrown, however when I change the stream by adding observeOn like that:
Observable.error<Int>(Exception()).startWith(1).observeOn(AndroidSchedulers.mainThread()).subscribe {
println("Item is $it")
}
onNext is never called. Only the exception is thrown. What am I missing here?
From the observeOn document
Note that onError notifications will cut ahead of onNext notifications
on the emission thread if Scheduler is truly asynchronous.
That means when you apply it, the onError is emitted first & hence the onNext is not called as the streams has ended due to onError.
You can do the following in order to receive the onNext first
observeOn(AndroidSchedulers.mainThread(), true)
This tells the Observable to delay the error till the onNext of startWith is passed
Related
I am trying to implement cache then network strategy for my API call using Kotlin Flows.
Here is what I am trying right now
flowOf(
remoteDataSource.getDataFromCache() // suspending function returning Flow<Data>
.catch { error -> Timber.e(error) },
remoteDataSource.getDataFromServer() // suspending function returning Flow<Data>
).flattenConcat().collect {
Timber.i("Response Received")
}
Problem here is collect is only called when getDataFromServer returns. My expectation is that I should get first event from cache and then second event from server after a few milliseconds. In this case "Response Received"gets printed twice but immediately one after other.
In this other variant "Response Received" only gets printed once that is after getDataFromServer() returns.
remoteDataSource.getDataFromCache() // suspending function returning Flow<Data>
.catch { error -> Timber.e(error) }
.flatMapConcat {
remoteDataSource.getDataFromServer() // suspending function returning Flow<Data>
}
.collect {
Timber.i("Response Received")
}
I was using RxJava's Flowable.concat() before and it was working perfectly. Is there something in Kotlin Flows which can emulate that behaviour?
Problem here is collect is only called when getDataFromServer returns.
The first problematic thing with your design is that the Flow-returning function is also suspendable. That's two layers of suspendability. Functions should return flows without any delays and the flows themselves should emit items as they come in. If you followed this guideline, your initial code would already work.
The way you wrote these functions, they can still work if you write this:
flow<String> {
emitAll(getCached())
emitAll(getFromServer())
}
This statement completes immediately, returning a cold flow. When you call collect on it, it first calls getCached() and emits the cached value, and then calls getFromServer() and emits the server response.
The above solution starts the server call only after you consume the cached value. If you need the two flows to be active concurrently, use flatMapMerge.
Assuming you fixed the above basic problem and made your Flow-returning functions non-suspending, all you need is this:
flowOf(getCached(), getFromServer()).flattenMerge()
If for some reason you can't do that, you have to add the emitAll wrapper around each call:
flowOf(
flow { emitAll(getCached()) },
flow { emitAll(getFromServer()) }
).flattenMerge()
Recently, merge operator was added to the Kotlin coroutines version 1.3.3. Here is the merged PR.
Using the merge operator, you should be able to get the result as and when it arrives.
Turns out in case of flowOf(someOperation()) someOperation() needs to be completed for downstream to start processing. Its like Observable.just(someOperation()) in RxJava world.
In second scenario flatMapConcat is actually a transform operator so it obviously returns final processed output.
There seems to be lack of native concat like operators in Flow world. This is how I solved this problem in the end
flow {
remoteDataSource.getDataFromCache()
.catch { error -> Timber.e(error) }
.onCompletion {
remoteDataSource.getDataFromServer()
.collect {
emit(it)
}
}.collect { emit(it) }
}
When I create 5 observables and subscribe to each of them with separate subscriber, intuitively I thought that each subscriber would get its observables' corresponding data, emitted via onNext() call:
val compositeSubscription = CompositeDisposable()
fun test() {
for (i in 0..5) {
compositeSubscription.add (Observable.create<String>(object : ObservableOnSubscribe<String> {
override fun subscribe(emitter: ObservableEmitter<String>) {
emitter.onNext("somestring")
emitter.onComplete()
}
}).subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Logger.i("testIt onNext")
}, {
Logger.i("testIt onError")
}))
}
}
However, what I see is one or two "testIt onNext" in the log.
Now, when I add the delay in subscribers' onNext(), all 6 subscribers onNext() are getting called.
This seems like some racy condition, when some of the subscribers are not fast enough to catch up on their data. Just how this happens evades me, as subscribe() should be called after Subscriber is up and running.
Would be grateful for any tips on this.
Judging from this code every subscriber should print "testIt onNext". Are you sure it is not getting printed? Maybe Android Studio is collapsing identical lines? Have you tried printing something different for each subscriber?
I have a number of Observables that are used for network requests in my app. Since so much is the same, I apply an Observable transformation to them:
/**
* Creates a transformer that applies the schedulers and error handling for all of the observables in this ViewModel.
*/
private fun applyTransformations(): Observable.Transformer<NetworkState, NetworkState> {
return Observable.Transformer { observable ->
observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.onErrorReturn { NetworkState.Error(it) }
.doOnNext { publishState(it) }
.startWith(NetworkState.Loading())
}
}
The goals I am trying to accomplish with the above:
Apply consistent schedulers
Handle any error by returning an instance of my sealed class.
Handle any onNext by publishing the state returned by the observable.
Start off by sending a Loading state.
This works mostly fine, but what I've noticed is that while I call startWith and a loading state, it is never actually handled by doOnNext(). In other words, publishState() is never called for my loading state.
Where I set up the observables, I don't bother to add a subscriber, because the doOnNext() above is all that I'll need:
val subscription = repository.getInstagramPhotos(count)
.map { mapIGPhotoResponse(it) }
.compose(applyTransformations())
.subscribe()
If I were to supply a subscriber above, though, it would handle the loading state. It would also handle two onNext() calls - one for the subscriber supplied, and one for the doOnNext in the transform.
Is there a way to modify this startWith call to emit to whatever I've specified in doOnNext? I'm using RxJava 1.
Edit: Just to clarify some more, if I track what's emitted I expect to see two things. Loading -> Success. What I actually see is just Success. If I supply a subscriber to the observable I see Loading -> Success -> Success.
startWith should be before doOnNext.
Rxjava methods, though they look like they use the builder pattern, actually don't. They return a new observable each time an operator is applied. In your case, your doOnNext observable completes before your start with observable, so it's consumer isn't called with what you supply in startWith.
Ideally, you should go with:
observable
.startWith(NetworkState.Loading())
.doOnNext { publishState(it) }
.onErrorReturn { NetworkState.Error(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
Also, be careful with subscribing with no Consumer for onEror should it happen. Since you have nothing to consume the onError, RxJava will crash your app since it has nothing to notify for the error. Consider replacing the doOnNext with a Success Consumer in subscribe, and an empty Consumer for the error if you want to ignore it.
Also doOnNext is typically used for side effects, such as logging and the sort, they're more of a convenience than true functional operators.
Here is a sample Rx chain using RxBindings:
RxView.clicks(refreshIcon)
.flatMap { Observable.error<Throwable>(IllegalArgumentException()) }
.subscribe(
{ Timber.d("onNext")},
{ error -> Timber.d("onError") })
.addTo(disposables)
After clicking my refresh icon once, this chain will not run again as a terminal event took place. I am guessing I was under the wrong impression that the subscription takes place whenever a click event is detected, and that it in fact subscribes whenever that block of code gets executed.
Question is how can I make my chain execute/re-execute on every click, even after it hits a terminal event? Looking for something intuitive.
Observable must complete when the first error occur, it's in their contract. In order to have your Observable survive terminal event, you will have to dig in RxJava Error handling operators. retry() seems a good fit in your case:
RxView.clicks(refreshIcon)
.flatMap { ... }
.retry()
.subscribe(...)
.addTo(disposables)
It is part of the Rx contract when an error occurred the stream will receive a onError event and will terminate.
Unless you actively handle the error, using for example: onErrorResumeNext()
I'm feeling a bit curious about how .subscribeOn() actually works on RxJava.
I have this piece of code that works as intended:
return endpoints.getRecentConversations(page)
.map().flatMap().doOnNext() //etc etc...
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
I was reading this article, trying to understand what the difference between subscribeOn and observeOn when this line caught my eye:
Position does not matter
subscribeOn can be put in any place in the stream because it affects
only the time of subscription
Which sounds perfectly fine. But I was feeling a bit skeptical I decided to test it. So I changed the code above (switched lines 2 and 3):
return endpoints.getRecentConversations(page)
.subscribeOn(Schedulers.io())
.map().flatMap().doOnNext() //etc etc...
.observeOn(AndroidSchedulers.mainThread())
As a result, I get an premature onComplete() on my subscriber. onNext() is never called and no errors are present in my logcat.
I can leave things the way they were, but I'd like to know why this is happening. Is it true that position doesn't matter? Is it something wrong with my code? Here's how my code looks
Yes, the position does matter a lot in RxJava, It's called upstream & downstream.
It's because you are subscribing to a hot observable
Watch this video to understand it better: Common RxJava Mistakes
https://www.youtube.com/watch?v=QdmkXL7XikQ&t=768s
There are two types of observables: Hot & cold.
A “hot” Observable may begin emitting items as soon as it is created,
and so any observer who later subscribes to that Observable may start
observing the sequence somewhere in the middle. A “cold” Observable,
on the other hand waits until an observer subscribes to it before it
begins to emit items, and so such an observer is guaranteed to see the
whole sequence from the beginning.