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()})
}
Related
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).
There are cases when I need to chain RxJava calls.
The simplest one:
ViewModel:
fun onResetPassword(email: String) {
...
val subscription = mTokenRepository.resetPassword(email)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(
//UI update calls
)
...
}
My Repository:
fun resetPassword(email: String): Single<ResetPassword> {
return Single.create { emitter ->
val subscription = mSomeApiInterface.resetPassword(email)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({
emitter.onSuccess(...)
}, { throwable ->
emitter.onError(throwable)
})
...
}
}
My Question
Do I need to Add:
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
for both calls to avoid any app freeze? or the second one for API call is enough?
No, you don't need to add
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
for the repo and the viewmodel.
.observeOn usually should be called right before handling the ui rendering. So usually, you'll need it in the ViewModel right before updating the ui or emitting the LiveData values.
Also, you properly don't need to subscribe to mSomeApiInterface in your repo, I think it would be better off to just return in as it's from your method up the chain, somthing like this:
fun resetPassword(email: String): Single<ResetPassword> {
return mSomeApiInterface.resetPassword(email);
}
and if you have any mapping needed you can chain it normally
fun resetPassword(email: String): Single<ResetPassword> {
return mSomeApiInterface.resetPassword(email)
.map{it -> }
}
This way you can write your ViewModel code as follow
fun onResetPassword(email: String) {
...
// note the switcing between subscribeOn and observeOn
// the switching is in short: subscribeOn affects the upstream,
// while observeOn affects the downstream.
// So we want to do the work on IO thread, then deliver results
// back to the mainThread.
val subscription = mTokenRepository.resetPassword(email)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
//UI update calls
)
...
}
This will run the API request on the io thread, will returning the result on the mainThread, which is probably what you want. :)
This artical has some good examples and explanations for subscribeOn and observeOn, I strongly recommend checking it.
Observable<RequestFriendModel> folderAllCall = service.getUserRequestslist(urls.toString());
folderAllCall.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(result -> result.getRequested())
.subscribe(this::handleResults, this::handleError);
private void handleResults(List<Requested> folderList) {
if (folderList != null && folderList.size() != 0) {
usersList.addAll(folderList);
}
adapter.notifyDataSetChanged();
}
}
private void handleError(Throwable t) {
Toast.makeText(getContext(),t.getMessage(),Toast.LENGTH_LONG).show();
}
in interface:
#Headers({ "Content-Type: application/json;charset=UTF-8"})
#GET
Observable<RequestFriendModel> getUserRequestslist(#Url String url);
POJO model :
public class RequestFriendModel {
#SerializedName("requested")
#Expose
private List<Requested> requested = null;
public List<Requested> getRequested() {
return requested;
}
public void setRequested(List<Requested> requested) {
this.requested = requested;
}
}
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 using rxJava and I want to do a forEach of a list, and for every item, make a call, and then once those calls are finished, call another one.
This is my code
val flowableList = answerListCreated.map {
questionService.addAnswerToQuestion(
questionId,
it.id,
MyUtils.getAccessTokenFromLocalStorage(context = mContext!!)
)
}
disposable = Flowable.concat(flowableList)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
addCorrectsAnswersToQuestion(questionId)
}
But it's joining in the subscribe twice, and it should join in the subscribe once.
What I'm missing? I thought concat should be a good option because I've read that it does first the first job, and then when job1 is finished it starts the job2.
Well, also if necessary I can return Observable<T>, from now in my service I'm returning Flowable<T> to test this.
i think you need to do something like:
val disposable = Flowable.fromArray(answerListCreated)
.flatMap {
questionService.addAnswerToQuestion(
questionId,
it.id,
MyUtils.getAccessTokenFromLocalStorage(context = mContext!!)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
.toList()
.subscribe {
addCorrectsAnswersToQuestion(questionId)
}
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
}