I have an Activity which loads data from the network and has a "retry" button if the request fails which just re-makes the same network call. This is the simplified code:
public class MainActivity extends Activity {
private DisposableObserver<Data> disposableObserver;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
loadData();
}
});
}
private void loadData() {
disposableObserver = control.fetchFromNetwork().subscribeWith(new DisposableObserver<Data>() {
#Override
public void onNext(Data data) {
updateUI(data);
}
#Override
public void onError(Throwable throwable) {
showError();
}
#Override
public void onComplete() {
}
});
}
#Override
public void onDestroy() {
super.onDestroy();
if (disposableObserver != null && !disposableObserver.isDisposed()) {
disposableObserver.dispose();
}
}
}
For what it's worth, this is the method that creates the Observer:
public Observable<Data> fetchFromNetwork() {
return getService().fetchdata()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(new Consumer<Throwable>() {
#Override
public void accept(Throwable t) throws Exception {
exceptionHandler.handle(t);
}
});
}
I'm using a DisposableObserver so it can be properly disposed of in the Activity's onDestroy() method.
In this code, every button click will create a new Observable and subscribe to it, creating a leak since only the last one is disposed of in the onDestroy() method. My question is: is there a way to retry/replay this same observer which already exists without having to create a new one every time? Or, is there a better approach to this scenario?
So you need to create a newObservable every time, to avoid the leaking issue you can create a CompositeDisposable and use the add method that receives the Disposable created after calling the .subscribe(). Then on onDestroy() simply call clear() and it will dispose every not disposed Disposable.
Related
My callback (see onResume() below) is still getting called even after calling dispose() in onPause() of MyFragment.java. Why?
I don't think it's important, but: I call NetworkUtils.subscribeToAvgPriceUpdates() from multiple Fragments (only one is visible at once). In each Fragment I have one DisposableObserver and then when I switch to that fragment, I subscribe to data updates using that observer.
NetworkUtils.java:
public static void subscribeToAvgPriceUpdates(DisposableObserver<List<Result>> observer, CoinPriceUpdateCallback callback) {
if(observer != null && !observer.isDisposed())
observer.dispose();
observer = new DisposableObserver<List<Result>>() {
#Override
public void onNext(List<Result> results) {
callback.onUpdated(results);
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
#Override
public void onComplete() { }
};
MainApplication.apiProvider.getAPI().getAllTickersRx()
.repeatWhen(objectObservable -> objectObservable.delay(15000, TimeUnit.MILLISECONDS) )
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
MyFragment.java:
private DisposableObserver<List<Result>> tickerUpdateObserver;
#Override
public void onPause () {
if(tickerUpdateObserver != null)
tickerUpdateObserver.dispose();
super.onPause();
}
#Override
public void onResume() {
super.onResume();
NetworkUtils.subscribeToAvgPriceUpdates(tickerUpdateObserver, results -> {
// still getting called even after I switch to another fragment, why?
// shouldn't .dispose() in onPause() stop the updates?
});
}
The problem is that you create a new DisposableObserver inside the method but the field tickerUpdateObserver remains the same first instance, thus you don't have a reference to the newer observers.
You could just return the new DisposableObserver from the method and update the field:
public static DisposableObserver<List<Result>> subscribeToAvgPriceUpdates(
DisposableObserver<List<Result>> observer,
CoinPriceUpdateCallback callback) {
if(observer != null && !observer.isDisposed())
observer.dispose();
observer = new DisposableObserver<List<Result>>() {
#Override
public void onNext(List<Result> results) {
callback.onUpdated(results);
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
#Override
public void onComplete() { }
};
MainApplication.apiProvider.getAPI().getAllTickersRx()
.repeatWhen(objectObservable ->
objectObservable.delay(15000, TimeUnit.MILLISECONDS) )
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
return observer;
}
#Override
public void onResume() {
super.onResume();
tickerUpdateObserver = NetworkUtils.subscribeToAvgPriceUpdates(
tickerUpdateObserver, results -> {
// still getting called even after I switch to another fragment, why?
// shouldn't .dispose() in onPause() stop the updates?
});
}
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.
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();
}
}
}
I'm trying to dig into the wonders of the RxJava2 world, but I'm still pretty confused.
Basically I have to call an API when the user clicks on a button, so I'm using a Retrofit2 client which returns an Observable that I subscribe on the on click method of the button.
The issue is that when the button is clicked twice I'll get:
io.reactivex.exceptions.ProtocolViolationException: It is not allowed to subscribe with a(n) <package>.MainActivity$1 multiple times. Please create a fresh instance of <package>.MainActivity$1 and subscribe that to the target source instead.
If I dispose the observer after the onComplete the api won't be called as the subscription is invalidated.. Am I missing/misunderstanding something?
public class MainActivity extends AppCompatActivity {
#BindView(R.id.button) Button button;
private DisposableObserver<PopularGames[]> observer;
private Observable<PopularGames[]> popularGamesObservable;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
APIsInterface apiClient = MyApplication.getInstance(this).getApiClient();
popularGamesObservable = apiClient.getPopularGames();
observer = new DisposableObserver<PopularGames[]>() {
#Override
public void onNext(PopularGames[] result) {
Timber.d("onNext " + Arrays.asList(result));
}
#Override
public void onError(Throwable e) {
Timber.e("onError " + e);
}
#Override
public void onComplete() {
Timber.d("onComplete");
}
};
}
#OnClick(R.id.button)
public void onViewClicked() {
popularGamesObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(observer);
}
}
io.reactivex.exceptions.ProtocolViolationException is expected
What you can do
CompositeDisposable compositeDisposable = new CompositeDisposable();
Then
#OnClick(R.id.button)
public void onViewClicked() {
compositeDisposable.add( popularGamesObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<PopularGames[]>() {
#Override
public void onNext(PopularGames[] result) {
Timber.d("onNext " + Arrays.asList(result));
}
#Override
public void onError(Throwable e) {
Timber.e("onError " + e);
}
#Override
public void onComplete() {
Timber.d("onComplete");
}
}));
}
Then in onDestory
compositeDisposable.dispose();
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