Prevent dispose to cancel network request - android

I am using RxJava2 and Retrofit2 for handling network requests.
I have cycle where doOnNext should always be ran, but my Activity which is the observer calls dispose() when it is destroyed and that causes retrofit to cancel the request.
java.io.IOException: Canceled
Is there a way to let the request complete but only dispose the UI level observer?
mApi.doSomethingImportant()
.doOnNext(new Consumer<ImportantResponse>() {
#Override
public void accept(ImportantResponse response) throws Exception {
// Store data, should always get here if request is success
}
})
.doOnError(new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
// Store error, should always get here if request fails
}
})
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(AndroidSchedulers.mainThread())
.subscribe(observer); // observer reports success/fail on UI if not disposed
Thanks.

I found one way to achieve the wanted behaviour by wrapping the observer with another, but I'm sure there is some elegant way to do this.
...
.subscribe(new NonDisposableObserver<>(observer)
Where NonDisposableObserver class is following:
public class NonDisposableObserver<T> implements Observer<T> {
private DisposableObserver<T> observer;
public NonDisposableObserver(DisposableObserver<T> observer) {
this.observer = observer;
}
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(T t) {
if (!observer.isDisposed()) {
observer.onNext(t);
}
}
#Override
public void onError(Throwable e) {
if (!observer.isDisposed()) {
observer.onError(e);
}
}
#Override
public void onComplete() {
if (!observer.isDisposed()) {
observer.onComplete();
}
}
}

Related

RxJava - ReplaySubject only emitting data twice

I am new to ReactiveX and I have a case where I want my observable to emit data to a late subscriber(whenever the observer subscribes, observable should emit the same data that it emitted previously). I made this Observable class that provide ReplaySubject's same instance to all observers (it is singleton class).
public class AccountsObservable {
private static ConnectableObservable<String> hotObservable;
private static AccountsObservable accountsObservable;
public static AccountsObservable getObject() {
if (accountsObservable == null) {
accountsObservable = new AccountsObservable();
}
return accountsObservable;
}
public ConnectableObservable<String> getObservable() {
if (hotObservable == null) {
Observable<String> observable = ReplaySubject.create(new ObservableOnSubscribe<String>() {
#Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
emitter.onNext("XYZ");
emitter.onComplete();
}
});
hotObservable = observable.replay();//publish
}
return hotObservable;
}
}
Similarly, this is the observer class that creates new observer instance.
public class AccountsObserver {
AccountsFetchListener listener;
public AccountsObserver(AccountsFetchListener listener) {
this.listener = listener;
}
public Observer<String> getObserver() {
return new Observer<String>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String accounts) {
listener.onSuccess(accounts);
}
#Override
public void onError(Throwable e) {
listener.onFailure();
}
#Override
public void onComplete() {
}
};
}
public interface AccountsFetchListener {
void onSuccess(String accounts);
void onFailure();
}
}
Here is the function where I test these observables
private void testObs() {
ConnectableObservable<String> observable = AccountsObservable.getObject().getObservable();
Observer<String> observer = new AccountsObserver(new AccountsObserver.AccountsFetchListener() {
#Override
public void onSuccess(String accounts) {
Log.e("DATA -> ", accounts);
}
#Override
public void onFailure() {
}
}).getObserver();
observable.subscribe(observer);
observable.connect();
}
I called this function "testObs()" 5 times but it emitted data only 2 times. The problem seems to be in AccountsObservable class where I provide ReplaySUbject's instance. Thanks
Your code runs fine as it is, your logs are being suppressed in logcat as per this:
We declared an application as too chatty once it logs more than 5 lines a second. Please file a bug against the application's owner that is producing this developer-verbose-debug-level class logging spam. The logs are 256KB, that means the application is creating a DOS attack and shortening the logs timepan to 6 seconds(!) making it useless for all others.
You can avoid this behaviour by whitelisting your app for logcat:
adb logcat -P '<pid or uid of your app>'

How to throw error from completable

I am a beginner for rxjava,room and mvvm architecture.I am trying to fetch a user from room database, using Rxjava.
when I get user successfully I want to show a toast and start another activity. and in case of failure, I will show an error message in text input layout.
I have try to do that using following code.
in my Activity, I have a method authorizeUser() which is called on button click.
private void authorizeUser() {
loginViewModel.checkInDb()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
Log.e("Action", "Complete");
showToast();
startAnotherActivity();
}
#Override
public void onError(Throwable e) {
Log.e("Action", "error");
showError();
}
});
}
In my ViewModel
public Completable checkInDb() {
Completable completable= Completable.fromAction(() ->
userDataSource.getSingleRecordFromName(name.get(), password.get())
.subscribe(new SingleObserver<User>() {
#Override
public void onSubscribe(Disposable d) {
isLoading.set(true);
}
#Override
public void onSuccess(User user) {
preference.save(Constants.CURRENT_USER, user)
.subscribe();
isLoading.set(false);
isComplete=true;
Log.e("got","success");
}
#Override
public void onError(Throwable e) {
isLoading.set(false);
passwordError.postValue(new Error("Username or password is incorrect"));
Log.e("got","failure");
isComplete=false;
}
}));
return completable;
}
in UserDataSource class
public Single<User> getSingleRecordFromName(String strName) {
return daoAccess.getSingleRecord(strName);
}
and in DaoAccess class
public Single<User> getSingleRecordFromName(String strName) {
return daoAccess.getSingleRecord(strName);
}
Using above code, onComplete method is always called in activity, weather in view model, user fetched successfully or not
But what I want to do is, I want to throw an error or send a notification to the activity when there is error thrown (or on Error method is called in viewmodel). so that I can display error on my activity.
May be my question can be silly, but I am new to this. Please help me.
Although it would be better to maybe wrap the response in LiveData so you would get the subscription/lifecycle "for free", unless there is a specific need to have a completable on the UI, which I don't really any reason for it).
I would instead change your Dao to return Observable instead of Single, and then (one again this is not the optimal solution, should wrap into LiveData) you can return that Observable to the UI:
public Observable checkInDb() {
return userDataSource.getSingleRecordFromName(name.get(), password.get())
.subscribe(new SingleObserver<User>() {
#Override
public void onSubscribe(Disposable d) {
isLoading.set(true);
}
#Override
public void onSuccess(User user) {
preference.save(Constants.CURRENT_USER, user)
.subscribe();
isLoading.set(false);
isComplete=true;
Log.e("got","success");
}
#Override
public void onError(Throwable e) {
isLoading.set(false);
passwordError.postValue(new Error("Username or password is incorrect"));
Log.e("got","failure");
isComplete=false;
}
}));
}
And change your Daos
in UserDataSource class
public Observable<User> getSingleRecordFromName(String strName) {
return daoAccess.getSingleRecord(strName);
}
and in DaoAccess class
public Observable<User> getSingleRecordFromName(String strName) {
return daoAccess.getSingleRecord(strName);
}
So now your view can checkInDb() and handle these cases.
PS: I'm assuming this daoAccess is not an API call rather a local DB (probably Room).

Use RxView.clicks (subscribe does not work)

I am new to reactive programming (RxJava and RxAndroid). I want to use RxView.clicks() instead of a click Listener. I put a Button into main layer and with Butterknife and in onCreate method Main activity I write this statement:
**//onCreate**
ButterKnife.bind(this);
RxView.clicks(btn_range)
.switchMap(new Function<Object, Observable<Integer>>() {
#Override
public Observable<Integer> apply(Object o) throws Exception {
return Observable.range(1,10);
}
})
.subscribe(new Observer<Integer>() {
#Override
public void onSubscribe(Disposable d) {
d.dispose();
}
#Override
public void onNext(Integer integer) {
Toast.makeText(MainActivity.this, integer+"", Toast.LENGTH_SHORT).show();
}
#Override
public void onError(Throwable e) {
Toast.makeText(MainActivity.this, e.getMessage()+"", Toast.LENGTH_SHORT).show();
}
#Override
public void onComplete() {
}
});
but when I run it, no toast appears.
I have converted my click listener to an observable and then I have changed the observable to a range of integer and finally I display it.
In your .subscribe() the Observer<Integer> calls d.dispose() as soon as it is subscribed.
So if your chain is disposed then it is not working anymore. The Disposable should be disposed when you no longer need the flow.
You could store the emitted Disposable and dispose it in the opposite lifecycle event callback to where you have subscribed it.

Can Publish Subject have multiple subscribers

I'm trying to subscribe following function, but functions's subscribe function doesn't receive any data, but error function does. I realized that if I create second subscriber inside of function it doesn't receive data too. I was able to add multiple subscriber to subject when using Rx-JS. Is it same for Rx-Java or is there different behavior, or am I doing something completely wrong?
public static PublishSubject<Boolean> doSomeWork() {
final PublishSubject<Boolean> res = PublishSubject.create();
doSomeOtherWork().subscribe(new Consumer<Boolean>() {
#Override
public void accept(#NonNull Boolean aBoolean) throws Exception {
res.onNext(true);
}
});
res.subscribe(new Consumer<Boolean>() {
#Override
public void accept(#NonNull Boolean bool) throws Exception {
//receives data
}
}, new Consumer<Throwable>() {
#Override
public void accept(#NonNull Throwable throwable) throws Exception {
handleSomeStuff()
.subscribe(new Consumer<Boolean>() {
#Override
public void accept(#NonNull Boolean aBoolean) throws Exception {
res.onNext(aBoolean);
}
});
}
});
return res;
}
The problem here is the order in which you are doing things. You should subscribe to res before you subscribe to doSomeWork() - otherwise if doSomeWork() emits before the res.subscribe(.. line, nobody is around to receive the emission.

RxAndroid not returning Subcription for the observer

I have started learning RxAndroid and below is the code I wrote to iterate over a model object (Results) that contains data fetched from the server. I'm iterating over the model object in the observable and providing a newly created object in the observer. I'm trying to take subscription of the observer to unsubscribe the task upon Orientation changes of the fragment. However the subscribe() returns VOID instead of subscription object.
Questions:
Does the latest version of RxAndroid handle unsubscription itself upon configuration/orientation change?
In case configuration change happens before the task is complete, the only way to restart this task that I can think of is, I persist the server response in onSavedInstance() and retrieve it from bundle when the fragment is recreated. It'll require booleans to figure out if the configuration change happened before the configuration change or not. Is there a graceful and cleaner way of coping with this?
private void createComicList(final List<Result> marvelResults) {
final MarvelComics marvelComics = new MarvelComics();
Observable marvelObservable2 = Observable.create(new ObservableOnSubscribe<MarvelComic>() {
#Override
public void subscribe(ObservableEmitter<MarvelComic> e) throws Exception {
for(Result result : marvelResults) {
MarvelComic marvelComic = new MarvelComic();
marvelComic.setDescription(result.getDescription());
marvelComic.setTitle(result.getTitle());
marvelComic.setPageCount(result.getPageCount());
marvelComic.setThumbnailUrl(result.getThumbnail().getPath());
marvelComic.setId(result.getId());
e.onNext(marvelComic);
}
e.onComplete();
}
});
marvelObservable2.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<MarvelComic>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(MarvelComic comic) {
marvelComics.getMarvelComicList().add(comic);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
showToast();
}
});
}
The Observable.subscribe(Observer<? super T>) method returns void in the 2.x since the Observer.onSubscribe(Disposable) is there to get the cancellation support that used to be Subscription in 1.x.
final CompositeDisposable composite = new CompositeDisposable();
Observable<Integer> source = Observable.just(1)
source.subscribe(new Observer<Integer>() {
#Override public void onSubscribe(Disposable d) {
composite.add(d); // <---------------------------------------------
}
#Override public void onNext(Integer t) {
System.out.println(t);
}
#Override public void onError(Throwable e) {
e.printStackTrace();
}
#Override public void onComplete() {
System.out.println("Done");
}
});
composite.add(source
.subscribeWith( // <-----------------------------------------------
new DisposableObserver<Integer>() {
#Override public void onNext(Integer t) {
System.out.println(t);
}
#Override public void onError(Throwable e) {
e.printStackTrace();
}
#Override public void onComplete() {
System.out.println("Done");
}
});
subscribe() method of Observable returns Subscription object in earlier versions of RxJava and current version returns an object of Disposble class which you can unsubscribe by invoking dispose() method.
For your second question you may check this answer Best practice: AsyncTask during orientation change

Categories

Resources