Android rxjava2 subscription within OnClick - android

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();

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 reuse an Observer in case of failure?

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.

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

Using RxAndroid ,after receiving data from network where (in which method ) I should call the data insertion part in DB?

I receive data from network ,I implemented that part, but after receiving data from server in other thread, I want to save the data in Data base,which I want to implement not in main thread,so after receiving data where I should call the DB insertion method.Here is my code
Observable<List<PhotoAlbum>> searchResponseObservable =
mService.getAPI().getAlbums().subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
searchResponseObservable.subscribe(new Observer<List<PhotoAlbum>>() {
#Override
public void onCompleted() {
Log.i("test","onComplete");
}
#Override
public void onError(Throwable e) {
Log.i("test","onComplete");
}
#Override
public void onNext(List<PhotoAlbum> photoAlbums) {
view().showSearchResult(photoAlbums);
}
});
You can use doOnNext() to insert received data into your DB like this:
Observable<List<PhotoAlbum>> searchResponseObservable = mService.getAPI()
.getAlbums()
.subscribeOn(Schedulers.newThread())
.doOnNext(photoAlbums -> insertIntoDb(photoAlbums))
.observeOn(AndroidSchedulers.mainThread());
searchResponseObservable.subscribe(new Observer<List<PhotoAlbum>>() {
#Override
public void onCompleted() {
Log.i("test","onComplete");
}
#Override
public void onError(Throwable e) {
Log.i("test","onComplete");
}
#Override
public void onNext(List<PhotoAlbum> photoAlbums) {
view().showSearchResult(photoAlbums);
}
});
Use flatMap to save in database and use Schedulers IO (Schedulers.io()) for background process
mService.getAPI().getAlbums().flatMap(new Func1<List<PhotoAlbum>, Observable<GetDriversResponse>>() {
#Override
public Observable<GetDriversResponse> call(GetDriversResponse mResponse) {
return mDatabaseHelper.setMyBus(mResponse);
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<GetDriversResponse>() {
#Override
public void call(List<PhotoAlbum> photoAlbums{view().showSearchResult(photoAlbums); }}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
throwable.printTrack
}
}));

How chain Retrofit calls?

How can I make one retrofit 2 call after another?
I'm reading about RxJava and I'm already doing my calls using RxJava, but I havn't found a good exemple of how to use flatMaps.
Can someone explain how to do it to me?
I'm trying to make these two calls, and after they're both done, I want to start a new activity.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://api.openweathermap.org/data/2.5/")
.build();
WeatherService weatherService = retrofit.create(WeatherService.class);
final Observable<Weather> london = weatherService.getCurrent();
london.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Weather>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Weather weather) {
Log.i("WEATHER","Weather Name: " + weather.getName());
}
});
final Observable<Wind> windObservable = weatherService.getWind();
windObservable.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Wind>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Wind wind) {
Log.i("WEATHER","Wind: " + wind.getSpeed().toString());
}
});
}
}
Maybe this link: https://github.com/ReactiveX/RxJava/wiki/Combining-observables will help. Checkout for zip. Eventually switchMap method may be useful in Your case.
Edit:
Maybe this example http://joluet.github.io/blog/2014/07/07/rxjava-retrofit/ will help You even more.
Edit #2: Some code
login().switchMap(new Func1<FirstResponse, Observable<SecondResponse>>() {
#Override
public Observable<SecondResponse> call(FirstResponse t) {
if (ApiUtils.isLoginValid(t)) {
return profile(t.getToken());
}
else{
return Observable.error(new CustomException());
}
}
}
}).subscribe(subscriber());
Note: profile method return type is is Observable<SecondResponse> and subscriber method type is Subscriber<? super SecondResponse>
You can either use flatMap as in the example or concatWith:
static Observable<Integer> intObservable() {
return Observable.just(1).delay(1, TimeUnit.SECONDS);
}
static Observable<String> stringObservable() {
return Observable.interval(1, TimeUnit.SECONDS).take(2).map(v -> v.toString());
}
public static void main(String[] args) {
intObservable()
.doOnNext(System.out::println)
.ignoreElements()
.cast(Object.class)
.concatWith(stringObservable()
.doOnNext(System.out::println)
.ignoreElements())
.toBlocking()
.subscribe();
}
However, you need some cast in case the two sources have different types.
You have multiple options here. One of them is to use zip:
london.zipWith(weatherService, new Func2<Weather, Wind, Something>() {
#Override
public Something call(final Weather weather, final Wind wind) {
...
return something;
}
}).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<Something>() {
#Override
public void onCompleted() {
}
#Override
public void onError(final Throwable e) {
}
#Override
public void onNext(final Something something) {
}
});

Categories

Resources