I am trying to learn RxJava2, and converting my AsyncTasks to Observables.
I have the following piece of code that I am trying to convert.
if(asyncTask.getStatus() == AsyncTask.Status.RUNNING){
asyncTask.cancel();
}
asyncTask = new CustomTask();
asyncTask.execute(input);
I tried to recreate the following with Disposables.
Disposable currentTask;
PublishSubject input = PublishSubject.create();
For every input
if(currentTask != null) currentTask.dispose();
currentTask = input
.map(// Network calls
// returns CustomObject)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
// do work with result
}, throwable -> Log.e(TAG, throwable.toString()));
However, currentTask is always null. Why? Is this the wrong way to do it??
You're using Disposable correctly but I can only assume you're messing up somewhere with the subject. Subjects in rx can be both publishers and subscribers... and subjects don't necessarily wait until subscribe(...) to start emitting items. For that reason, I wouldn't suggest replacing your AsyncTasks with any kind of Subject.
You can get similar, more deterministic behavior, doing this though:
Observable<CustomObject> networkObservable =
Observable.create(emitter ->
{
try {
CustomObject object = doNetworking();
emitter.onNext(object);
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
}
);
if(currentTask != null) currentTask.dispose();
currentTask = networkObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
// this next subscribe is similar to AsyncTask.execute() as it starts the stream
.subscribe(result -> {
// do work with result
}, throwable -> Log.e(TAG, throwable.toString()));
Also, consider looking into SerialDisposable and you don't have to do those null/dispose checks
SerialDisposable serialDisposable = new SerialDisposable();
Atomically: set the next disposable on this container and dispose the previous one (if any) or dispose next if the container has been disposed.
Disposable disposable = networkObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...);
serialDisposable.set(disposable); // this will call dispose on the previous task if it exists
Related
Are there any clear and smart solution to dispose observable from its subscribe method?
E.g. I have an Observable.interval that emits Long every second. In my subscribe method i want to check if 20 seconds gone than dismiss subscription.
val observable = Observable.interval(1000,TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ num ->
if(num == 20.toLong())
{
observable.dispose()//I cant call dispose here because variable 'observable' is not avaliable yet
}
})
What is the easiest and right way to achieve this logic?
I found one simple solution. Just divide variable declaration and initialization in to two steps.
E.g.
var observable:Disposable? = null
observable = Observable.interval(1000,TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ num ->
if(num == 20.toLong())
{
observable?.dispose()
}
})
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'm following Kaushik Gopal's implementation of MVI with a ViewStates Observable and a ViewEffects Observable. Example can be seen here: https://github.com/kaushikgopal/movies-usf/blob/master/app/src/main/java/co/kaush/msusf/movies/MSMovieVm.kt
Relevant snippet:
init {
eventEmitter
.doOnNext { Timber.d("[Event]: $it") }
.eventToResult()
.doOnNext { Timber.d("[Result]: $it") }
.share()
.also { result ->
viewStates = result
.resultToViewState()
.doOnNext { Timber.d("[ViewState]: $it") }
.replay(1)
.autoConnect(1) { disposable = it }
.distinctUntilChanged()
viewEffects = result
.resultToViewEffect()
.doOnNext { Timber.d("[ViewEffect]: $it") }
}
}
The major difference is that I'm using Fragments instead of Activities. Everything works the first time around, but when I go from Fragment A to Fragment B, then back to Fragment A, the result observable triggers new ViewStates, but no new ViewEffects. Is there a special way I should be subscribing/unsubscribing or am I constructing the rx chain in a strange way? I currently subscribe to both the ViewStates and ViewEffects Observables in onViewCreated() and dispose in onViewDestroyed(). I've verified that both Observables are subscribed to in each case, it's just that when I come back to Fragment A, and input new ViewEvents, I get new ViewStates but not ViewEffects. Maybe it's a multi-casting issue?
Here's a simplified version of what I'm trying to do (using Kotlin and RxJava)
makeServerCall()
.doOnNext {
doStuff(it)
}
//TODO: if it == 0, call asyncOperation() and wait for its callback to fire
//before running the rest of the stream. Otherwise immediately run the rest
//of the stream
.flatMap {
observable1(it)
observable2(it)
Observable.merge(
getSpotSearchObservable(observable1),
getSpotSearchObservable(observable2)
}
.subscribeBy(onNext = {
allDone()
view?
})
How do I squeeze in the call to asyncOperation() and make the rest of the stream wait for its callback to fire, but only when a certain condition is met? This seems like it's probably a trivial operation in Rx, but no obvious solution is coming to mind.
FlatMap it!
.flatMap {
if (it == 0) {
return#flatMap asyncOperation()
.ignoreElements()
.andThen(Observable.just(0))
}
return#flatMap Observable.just(it)
}
.flatMap {
observable1(it)
observable2(it)
Observable.merge(
getSpotSearchObservable(observable1),
getSpotSearchObservable(observable2)
)
}
I am looking to poll the backend call for certain number of times for a predefined regular intervals. I would like to exit the loop if I have received an expected payload in between the loop and update the UI else terminate the polling.
Below is the code I normally do when I make standard http call.
//Response Model from backend API
public class ApplicationStatusResponse
{
public boolean isActive;
}
//Retrofit facade
#POST(v1/api/applicationStatus)
Single<ApplicationStatusResponse> checkApplicationStatus(#Body ApplicationStatusRequest applicationRequest);
-----
DisposableSingleObserver<ApplicationStatusResponse> disposableSingleObserver = new DisposableSingleObserver<ApplicationStatusResponse>() {
#Override
public void onSuccess(ApplicationStatusResponse response) {
// Update UI Here
}
#Override
public void onError(Throwable e) {
}
};
CompositeDisposable compositeDisposable = new CompositeDisposable();
// Following call works alaways works
DisposableSingleObserver<ApplicationStatusResponse> disposable = originationRepo.checkApplicationStatus(applicationStatusRequest)
.observeOn(schedulerProvider.mainThread())
.subscribeWith(disposableSingleObserver);
compositeDisposable.add(disposable);
But I am kind of lost here in the following code with the syntax error and I am not able to use the same disposableSingleObserver when calling from the Flowable.interval and need help with my use case where I need to update the UI the status regularly until the time is elapsed or status is active which ever happens first and also I am not after terminating the polling if I received HTTP Status Code of 500 instead repeat until the mentioned conditions are met.
//Help Needed here when I need polling in regular interval - I am kind of the syntax error complain from Android Studio
int INITIAL_DELAY = 0;
int POLLING_INTERVAL = 1000;
int POLL_COUNT = 8;
disposable = Flowable
.interval(INITIAL_DELAY, POLLING_INTERVAL, TimeUnit.MILLISECONDS)
.map(x -> originationRepo.checkApplicationStatus(applicationStatusRequest))
.take(POLL_COUNT) ??
// How can I receive the response payload and update the UI
compositeDisposable.add(disposable);
Appreciate your help in advance.
(in continuation with MyDogTom's answer you could also "short-circuit" the observable by throwing a custom Error/Exception)
Option 3:
disposable = Flowable
.interval(INITIAL_DELAY, POLLING_INTERVAL, TimeUnit.MILLISECONDS)
.map(x -> originationRepo.checkApplicationStatus(applicationStatusRequest)) // .flatMap (?)
.take(POLL_COUNT) //YES
.doOnNext() // update UI here
.map(response -> {
if(!response.checkCondition()) {
throw new ShortCircuitException();
}
return response.data();
})
.onErrorResumeNext(throwable -> (throwable instanceof ShortCircuitException)
? Observable.empty()
: Observable.error(throwable))
Option #1 Use filter + take(1)
disposable = Flowable
.interval(INITIAL_DELAY, POLLING_INTERVAL, TimeUnit.MILLISECONDS)
.map(x -> originationRepo.checkApplicationStatus(applicationStatusRequest))
.take(POLL_COUNT) //YES
.doOnNext() // update UI here
.map(response -> ) // should stop condition. true - stop, false - continue
.filter(!shouldContinue)
.take(1)
Option #2 Use Subject + takeUntil
Subject<Boolean> stopSubject = PublishSubject.create();
disposable = Flowable
.interval(INITIAL_DELAY, POLLING_INTERVAL, TimeUnit.MILLISECONDS)
.takeUntil(stopSubject.asObservable())
.map(x -> originationRepo.checkApplicationStatus(applicationStatusRequest))
.take(POLL_COUNT) //YES
.subscribe(
response -> {
//update UI
boolean shouldStop = ... // calculate
if (shouldStop) {
stopSubject.onNext(true);
}
}
...
)
PS. This is pseudo code. I hope you get idea.