I have a click event which needs to make a network request.
RxView.clicks(button)
.flatMapCompletable({ x -> networkCall() })
.subscribe(...)
The click is an Observable.
networkCall returns a Completable.
However the block inside subscribe is never called when i tap the button.
I've also tried
RxView.clicks(button)
.flatMap({ x -> networkCall().toObservable<Void>() })
.subscribe(...)
How can I get this to work so that each time I tap on the button, a network request is made and is then handled in the subscribe.
EDIT:
I haven't done the network stuff yet so currently it's just
public Completable networkCall() {
Completable.complete();
}
So it's guaranteed to complete.
The flatMap case needs items, otherwise its onComplete will never fire due to the already mentioned never-completing clicks source. For example:
RxView.clicks(button)
.flatMap({ x -> networkCall().andThen(Observable.just("irrelevant")) })
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ v -> System.out.println(v)}, { e -> e.printStackTrace() })
Related
I have a webservice call that return an object in which there is a parameter that indicates whether the operation ended successfully or not, so I would like to filter it (kind of if/else statement) inside the RxJava chain by using RxJava operators. Is it possible?
Something like this but not using if/else:
repo.webserviceCall(username, password)
.flatMap(result -> {
if (result.isSuccessful())
repo.secondWebserviceCall(result.getInfo())
else
showToastMessage("Api call not successful"); //STOP FLOW HERE
})
.flatMap(result -> thirdWebserviceCall(res))
.subscribe(res -> {showSuccessMssg(res)}, throwable -> { showError(t)});
You can return an error() from your flatMap so that the execution then goes to the onError consumer in your subscribe call.
If each service call returns one item, you could rearrange the operators so that not successful won't run the flatMap for the second and third calls. The filter will turn the setup to empty for which you can use the onComplete handler to display the toast.
repo.webserviceCall(username, password)
.filter(result -> result.isSuccessful())
.flatMap(result ->
repo.secondWebserviceCall(result.getInfo())
.flatMap(result -> thirdWebserviceCall(res))
)
.subscribe(
res -> showSuccessMssg(res),
throwable -> showError(t),
() -> showToastMessage("Api call not successful")
);
I am trying to chain two network calls in my Android app. I am using Retrofit. Basically I want to do :
Make API Call to login
Wait for the response of login, save the token to SharedPrefs
Make another API call right after I've saved the token
Wait for the response, save the data
I think I have chained the stream in the right way, the only thing is I want to update the UI in between. For example once the call starts I want to display a progressDialog ( I do that in doOnSubscribe ), or dismiss the Dialog once the call has completed ( I do that in doOnComplete ). However I get the exception Only the original thread that created a view hierarchy can touch its views. I subscribe on the io thread and observe on the mainThread so that I can make the changes to the UI, however I must be missing something.
I tried adding .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
lower in the stream, but I still get the same error message.
getView().onLoginAction().subscribe(aVoid -> Observable.combineLatest(
getView().userNameObservable().map(CharSequence::toString),
getView().passwordObservable().map(CharSequence::toString),
Pair::new)
.first()
.subscribe(usernamePasswordPair -> {
User user = User.create(usernamePasswordPair.first, usernamePasswordPair.second, "");
RetrofitClientInstance.createService(AuthenticationNetworkApi.class).login(new Login(user.username(), user.password()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(loginResponse -> {
AuthorizationResponse responseBody = loginResponse.body();
if (responseBody != null && responseBody.getAccessToken() != null && !responseBody.getAccessToken().isEmpty()) {
if (localStorage.getAccessToken().isEmpty()) {
localStorage.saveAccessToken(responseBody.getAccessToken());
}
}
}
).
doOnSubscribe( action -> getView().showProgressDialog())
.doOnError(error -> {
getView().dismissProgressDialog();
getView().showErrorMessage("Login Unsuccessful");
}).doOnComplete(() -> getView().dismissProgressDialog()
)
.flatMap(response -> RetrofitClientInstance.createService(ActivitiesApi.class).getUserActivities())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(activities -> {
for (UserActivityApiModel useractivity : activities
) {
activityService.addActivity(Activity.create(Integer.parseInt(useractivity.getId()), useractivity.getActivityName(), useractivity.getDate(),
Integer.parseInt(useractivity.getValue()), Integer.parseInt(useractivity.getSubCategory().getId())));
}
}).doOnError(error -> getView().showErrorMessage(error.getMessage()))
.doOnComplete(() -> getView().redirectToHomeScreen())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
}));
The error occurs here :
.doOnError(error -> {
getView().dismissProgressDialog();
getView().showErrorMessage("Login Unsuccessful");
})
It seems you are using a different thread to execute your backend. In that case, you can't touch the main UI thread from the second one. You need to execute first runOnUiThread { //your code }
In //your code, call the two lines of code that you put on doOnError.
I have a series of sequential Parse network calls that are all dependent on each other to save the final object all wrapped by an Observable so I can deal with everything as one call in my presenter.
If the user decides to change fragments, or leave the app or whatever, this network call is important enough that I'd like it to attempt to complete.
If I call disposables.dispose() the observable will throw an error (which I can catch) on the next .save method in the Observable and the network call does not finish.
If I don't dispose of it, the network call finishes, but it will call my onComplete and throw an error since the view is gone. I can stop that error from happening, but then I'm worried that I've created a memory leak.
I don't care if the oncomplete/onerror get called if the user gets into this situation but I would like to ensure it completes one way or another.
Is there any way to let the call complete, but not cause a memory leak by not disposing it?
fun doParseNetworkCall(){
return Observable.create<Boolean> { emitter ->
createParseObject1
createParseObject1.save()
createParseObject2
createParseObject2.add(key, createParseObject1)
createParseObject2.save()
createParseObject3
createParseObject3.add(key, createParseObject2)
createParseObject3.save()
emitter.onNext(true)
}
fun doNetworkCall(){
repo.doParseNetworkCall()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).subscribeBy(
onError = { error ->
//do something
},
onComplete = {
//do something
}
).addTo(disposable)
}
I was able to resolve my issue doing the following. I think it's memory safe since the 2nd set of observables don't subscribe until the first observable completes, and if the Composite Disposable in the presenter has been disposed of already, the 2nd set will not subscribe.
repo.saveSomething()
.map {
//Do some non view stuff
}
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).subscribeBy(
onError = { error ->
onErrorMethod()
},
onComplete = {
onSuccessMethod()
}
)
fun onSuccessMethod() {
Observable.just(true)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).subscribeBy(
onComplete = {
//Do view stuff
}).addTo(disposable)
}
fun onErrorMethod() {
Observable.just(true)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).subscribeBy(
onComplete = {
//Do view stuff
}).addTo(disposable)
}
My Question is probably more of the conceptual nature.
I get that by the Observable contract my Observable will not emit any more items after onComplete or onError is called.
But I'm using the RxBindings for Android and therefore it's not "my Observable" but the click on a Button that emits items.
fun observeForgotPasswordButton(): Disposable {
return view.observeForgotPasswordButton()
.flatMap {
authService.forgotPassword(email).toObservable<Any>()
}
.subscribe({
// on next
Timber.d("fun: onNext:")
}, { error ->
// on error
Timber.e(error, "fun: onError")
}, {
// onComplete
Timber.d("fun: onComplete")
})
}
observeForgotPasswordButton() returns an Observable
fun observeForgotPasswordButton(): Observable<Any> = RxView.clicks(b_forgot_password)
The problem is that authService.forgotPassword(email) is a Completable and it will call either onComplete or onError both of which lead to the fact that I cannot reuse the button anymore since the subscription ended.
Is there a way to circumvent this behavior?
Because in an error occurs I would like to be able to retry.
Also I would like it to be possible to send more then one password forgotten emails.
You can use the retry() and repeat() operators to automatically resubscribe to the original Observable (or Completable).
fun observeForgotPasswordButton(): Disposable {
return view.observeForgotPasswordButton()
.flatMap {
authService.forgotPassword(email).toObservable<Any>()
}
.repeat() // automatically resubscribe on completion
.retry() // automatically resubscribe on error
.subscribe({
// on next
Timber.d("fun: onNext:")
}, { error ->
// on error
Timber.e(error, "fun: onError")
}, {
// onComplete
Timber.d("fun: onComplete")
})
}
In android app i have this case:
Listen to my editText with observable:
WidgetObservable.text(myEditText, false)
.map { it.text().toString() }
.debounce(800, TimeUnit.MILLISECONDS, Schedulers.io())
Then i need to send network request with string emitted by observable:
.flatMap { networkObservable.subscribeOn(Schedulers.io()) }
My question is: what is the best possible way to write infinite stream of these network results.
Errors handled by UI.
Unsubscription done with AppObservable.bindActivity() wrapper
I ended up attaching materialize() operator to network observable, and then handling it like:
.subscribe{
when (it.getKind()) {
Kind.OnNext -> text.setText(it.getValue())
Kind.OnError -> text.setText(it.getThrowable().getMessage())
}
}
Do you know better way, or its just fine?
At least it works.
P.S. another useful case will be Refresh button clicks flatMap'ed to network calls
You can use onErrorResumeNext to recovery your Observable from a failure. E.g.,
WidgetObservable.text(myEditText, false)
.map { it.text().toString() }
.debounce(800, TimeUnit.MILLISECONDS, Schedulers.io())
.flatMap {
networkObservable.subscribeOn(Schedulers.io())
.onErrorResumeNext(t -> t.getMessage())
}