I use RXAndroidBle to connect to Bluetooth devices. I use establishConnection to get the connection observable and want to convert this Observable to an Completable. This code works and the completable completes as expected:
connectionObservable
.observeOn(AndroidSchedulers.mainThread())
.doOnNext {
...
startReadingData()
}
.doOnError { ... }
.take(1)
.ignoreElements()
whereas this never completes:
connectionObservable
.observeOn(AndroidSchedulers.mainThread())
.doOnNext {
...
startReadingData()
}
.doOnError { ... }
.flatMap { Observable.just(it) }
.ignoreElements() // flatMapCompletable { Completable.complete() } doesn't work either
So I'm purly asking out of interest, why does flatMap with Observable.just() not work, as Obsrevable.just() also completes immediately?
Problem
Never completes:
connectionObservable
.observeOn(AndroidSchedulers.mainThread())
.doOnNext {
...
startReadingData()
}
.doOnError { ... }
.flatMap { Observable.just(it) }
.ignoreElements() // flatMapCompletable { Completable.complete() } doesn't work either
This is actually quite simple. The connectionObservable is probably infinite. It will call onNext, but not onComplete. The downstream operators receive the onNext emit and process it accordingly. The flatMap operator only completes, when the upstream and the inner-stream emits onComplete. The inner-stream of flatMap completes, but not the source-observable. Therefore you do not get a terminal messages, ever.
Completes
connectionObservable
.observeOn(AndroidSchedulers.mainThread())
.doOnNext {
...
startReadingData()
}
.doOnError { ... }
.take(1)
.ignoreElements()
This stream completes, because there is a terminal operator. In this case you have a take(1). What does the Take-Operator do? It will wait for an onNext emit from source and transforms it to onNext(message) and onComplete(). You could add the flatMap with Observable.just as inner-stream below the take-Operator and it would still complete.
Take-Operator Impl
#Override
public void onNext(T t) {
if (!done && remaining-- > 0) {
boolean stop = remaining == 0;
downstream.onNext(t);
if (stop) {
onComplete();
}
}
}
The implementation of the Take-Operator in RxJava2 looks like this. It is clear, that a upstream onNext will result in a onNext and possibly a onComplete (downstream).
Related
I have the method below:
private fun bindUploadPhotos(uploadPhotoCommands: List<UploadPhotoCommand>): Disposable {
return Observable.fromIterable(uploadPhotoCommands)
.concatMapSingle { param ->
requestUploadPhoto.getSingle(param)
}
.doFinally {
onAllPhotosUploaded()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
onPhotoUploaded()
}
}
I upload photos sequentially. I expect doFinally to execute after all photos are uploaded successfully. However, it executes immediately.
I am not a RxJava2 master, so I gladly take your different approaches to achieve my purpose.
onComplete called when all your photos uploaded. so call onAllPhotosUploaded there
private fun bindUploadPhotos(uploadPhotoCommands: List<UploadPhotoCommand>): Disposable {
return Observable.fromIterable(uploadPhotoCommands)
.flatMapSingle { param ->
requestUploadPhoto.getSingle(param)
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe ({onPhotoUploaded},{},{onAllPhotosUploaded()})
}
I've created rx function to call a network call from view-model in android, it parses network on main thread function.
I just change few line of code it worked. but i need to know the reason for this because its use same builder pattern to create a rx-call.
once I tried with changing .doOnSubscribe() ,doOnComplete () , .applySchedulers() after the flatmap call it worked? how is this happened?
fun loadjobs(var countryID:String){
subscription.add(
repository.getMainJobsFromLocal(countryID)
.doOnSubscribe { postProgress(StatusModel(Status.IN_PROGRESS))}
.doOnComplete { postProgress(StatusModel(Status.COMPLETED)) }
.applySchedulers()
.flatMap {
if (it.isNullOrEmpty()) {
repository.getMainJobsFromServer(countryID)
} else {
Flowable.just(Response.success(it))
}
}
.subscribe({
if (it.isResponseOk()) {
postProgress(StatusModel(Status.SUCCESS))
mainJobResponse.postValue(it.body())
} else {
postProgress(StatusModel(Status.FAILED))
mainJobResponse.postValue(null)
}
}, {
postProgress(StatusModel(Status.FAILED))
mainJobResponse.postValue(null)
}))
}
fun loadjobs(var countryID){
subscription.add(
repository.getMainJobsFromLocal(countryID)
.flatMap {
if (it.isNullOrEmpty()) {
repository.getMainJobsFromServer(countryID).flatMap {
Flowable.just(it)
}
} else {
Flowable.just(Response.success(it))
}
}.doOnSubscribe { postProgress(StatusModel(Status.IN_PROGRESS)) }
.doOnComplete { postProgress(StatusModel(Status.COMPLETED)) }
.applySchedulers()
.subscribe({
if (it.isResponseOk()) {
postProgress(StatusModel(Status.SUCCESS))
mainJobResponse.postValue(it.body())
} else {
postProgress(StatusModel(Status.FAILED))
mainJobResponse.postValue(null)
}
}, {
postProgress(StatusModel(Status.FAILED))
mainJobResponse.postValue(null)
}))
}
applySchedulers() after the flatmap call it worked? how is this happened?
observeOn() affects everything downstream. If you have a flatMap() after observeOn(), it gets executed on that scheduler.
Similarly subscribeOn() affects the upstream chain.
For these reasons, for most use cases you'd want to have the schedulers applied at the end of your rx chain and not in the middle.
Add subscribeOn(Schedulers.io()) and observeOn(AndroidSchedulers.mainThread()) to your Observable.
I am searching for a doOn... callback for a ConnectableObservable that is invoked when every observer terminates
val gatewayItems = viewModel.getGatewayItems(gateways!!)
.observeOn(Schedulers.io())
.take(1)
.publish()
.autoConnect(2)
gatewayItems.subscribe { sharedGateways -> sharedGatewaysAdapter.submitList(sharedGateways) }
gatewayItems.subscribe { sharedGateways -> privateGatewaysAdapter.submitList(privateGateways) }
I would like to get a callback to my multicasted hot observable when both of my observers signal a terminal event
I have tried to put doOnTerminate and doOnComplete operators on my parent multicasted observable but, it seems that these callbacks are invoked 2 times (one for each observer)
val gatewayItems = viewModel.getGatewayItems(gateways!!)
.observeOn(Schedulers.io())
.take(1)
.doOnComplete { ... }
.doOnTerminate { ... }
.publish()
.autoConnect(2)
Both .doOnComplete and .doOnTerminate work for me.
Edit: the chances are you might be attaching the do... operators in incorrect order. For example, neither of these doOnComplete will work:
val gatewayItems = viewModel.getGatewayItems(gateways!!)
.observeOn(Schedulers.io())
.doOnComplete { ... }
.take(1)
.publish()
.autoConnect(2)
.doOnComplete { ... }
I`m struggling to retry my rxjava Single call after another network call is done in doOnError:
restApi.getStuff()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError {
getRefreshToken(it, AuthenticationManager.Callback{
retry(1)
})
}
.subscribeBy(
onSuccess = { response ->},
onError = { throwable ->}
)
But the retry method cannot be invoked inside the doOnError method.
Do you have any other ideas?
Eventually I used a different approach with creating an Interceptor for token authorization (#Skynet suggestion led me to it).
Here is more info about it:
Refreshing OAuth token using Retrofit without modifying all calls
if you want to check the response and then retry you should try this:
restApi.getStuff()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retryWhen(errors -> errors.flatMap(error -> {
// for some exceptions
if (error instanceof IOException) {
return Observable.just(null);
}
// otherwise
return Observable.error(error);
})
)
otherwise
restApi.getStuff()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry()
from the docs, retry() responds to onError. link
I'm using a travel API which I first hit with a request to create a session and then I use the session URL returned from that URL to then call until its status parameter returns UpdatesComplete.
Here's what I have so far:
lateinit var pollUrl: String
travelInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
pollUrl = url
travelInteractor.pollResults(pollUrl)
.retryWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
}
.doOnNext {
if (it.status != "UpdatesComplete") travelInteractor.pollResults(pollUrl)
.retryWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
}
.subscribe({
// Subscription stuff
)}
What's currently happening is that it will call doOnNext() and then it will make the network poll but I won't be capturing the subscription and also won't chain another poll. Is there a more efficient way I can be writing this?
Solution
Thanks to iagreen I managed to achieve this with:
lateinit var pollUrl: String
travelInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
travelInteractor.pollResults(url)
.retryWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
.repeatWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
.filter {
it.itineraries.map { ... } // Use response here appropriately and then check status
it.status == "UpdatesComplete"
}
.take(1)
}
.subscribe({
// Subscription stuff
)}
I am assuming your pollResults(url) method returns a Single or an Observable that behaves like a single -- it returns one result and then onComplete. If that is truly the case, you can use repeatWhen to retry the request on success and retryWhen to retry on error. See the code below.
skyScannerInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
skyScannerInteractor.pollResults(url)
.repeatWhen { complete -> complete.delay(1, TimeUnit.SECONDS) }
.retryWhen { errors -> errors.delay(1, TimeUnit.SECONDS) }
.filter({ it.status == "UpdatesComplete" })
.take(1) // Take the first valid value and complete
}
.subscribe({
// Subscription stuff
)}
A little explanation -
repeatWhen/retryWhen will try the request every second.
filter will cause elements with the wrong status to be ignored.
When you get the first status == "UpdatesComplete" element, take(1) will emit that value and complete -- this will have the effect of cancelling the retries.
Note: In the case of an error, retrying a network request forever is usually the wrong thing to do. I recommend you modify the retryWhen above to suit you use case to terminate in the event of network failure. For example, you could retry three times and then propagate the error. See this article for some examples on how you could do that. It is also a good reference on repeatWhen/retryWhen.
retry() and retryWhen() both respond to an onError event in the Observable, that is why it isn't actually retrying; you're not receiving those onError events in your pollResults() Observable. Right now, your retrying code doesn't actually depend on the JSON response.
There are two ways I would think about going about this:
Throw an exception in your pollResults() Observable if the JSON response is unsatisfactory. This should trigger the retryWhen(). You'll need to test for it in the Observable somewhere.
Reorganize your Observable like this:
`
lateinit var pollUrl: String
skyScannerInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
pollUrl = url
skyScannerInteractor.pollResults(pollUrl)
}
.doOnNext {
if (it.status != "UpdatesComplete") {
throw IOException("Updates not complete.") //Trigger onError
}
}
.retryWhen { //Retry the Observable (createSession) when onError is called
it.delay(1000, TimeUnit.MILLISECONDS)
}
.subscribe({
// Will give result only when UpdatesComplete
}