How to unsubscribe Single Observable in RxJava - android

I am looking to unsubscribe the Rx Singles after the operation.
This is how I have unsubscribed Observable in RxJava.
public class BooksRepositoryImpl implements IBooksRepository {
private ReplaySubject<Book> bookSubject;
private Subscription bookSubscription;
BooksApi BooksApi;
ISchedulerProvider scheduler;
#Inject
public BooksRepositoryImpl(BooksApi BooksApi, ISchedulerProvider scheduler) {
this.BooksApi = BooksApi;
this.scheduler = scheduler;
}
#Override
public Observable<Book> getBooks(String author) {
if (bookSubscription == null || bookSubscription.isUnsubscribed()) {
bookSubscription = ReplaySubject.create();
bookSubscription = BooksApi.getBooks(author)
.subscribeOn(scheduler.backgroundThread());
}
return bookSubscription.asObservable();
}
#Override
public void unbind() {
if (bookSubscription != null && !bookSubscription.isUnsubscribed())
bookSubscription.unsubscribe();
}
}
But I am not sure how to unsubscribe the Single Observable.
It would be great if someone can able to share some idea.
public class BooksRepositoryImpl implements IBooksRepository {
BooksApi BooksApi;
ISchedulerProvider scheduler;
#Inject
public BooksRepositoryImpl(BooksApi BooksApi, ISchedulerProvider scheduler) {
this.BooksApi = BooksApi;
this.scheduler = scheduler;
}
#Override
public Single<Book> getBooks(String author) {
return BooksApi.getBooks(author)
.subscribeOn(scheduler.backgroundThread());
}
#Override
public void unbind() {
//?? HOW Can I unsubscribe here
}
}

According to the documentation Single, you should do nothing, the subscription end by itself:
A Single will call only one of these methods, and will only call it
once. Upon calling either method, the Single terminates and the
subscription to it ends.
Hope this helps.
Sorry for my english.

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>'

PublishSubject subscriber is not receiving events

I have a class ViewModel that exposes a PublishSubject binder.
ViewModel
public class ViewModel {
private PublishSubject<ActionsEvent> binder = PublishSubject.create();
private Service service = createService();
#Override
public Observable<ActionsEvent> getBinder() {
return binder.doOnSubscribe(initialize());
}
private Action0 initialize() {
return new Action0() {
#Override
public void call() {
service.getActions().subscribe(new Action1<Action>() {
#Override
public void call(Action action) {
Log.d(TAG, "So far, so good");
binder.onNext(new ActionFetchedEvent(action));
}
});
}
};
}
}
And in the Activity, it subscribe an action to be executed when each event is fetched.
Activity
public class MyActivity extends Activity {
#Override
public void onCreate(Bundle savedInstance) {
//More code
viewModel.getBinder().subscribe(new Action1<ActionsEvent>() {
#Override
public void call(ActionsEvent event) {
Log.d(TAG, "This is not printed!!");
paintActionInUserInterface(event.getAction());
}
});
}
}
Service
public interface ActionsService {
#GET("/actions")
Observable<Action> getActions(); //Performs an HTTP request with Retrofit
}
ActionFetchedEvent
public class ActionFetchedEvent implements ActionsEvent {
private Action action;
//getters and setters
}
But subscriber doesn't receive the event. Why?
it is because you do not create an Subject with .create() factory-method, and onSubscribe will be called before the callback of your subscription, so you will subscribe too late and miss the element. You could use a BahaviourSubject, which will replay the last element, if you subscribe.
Could you please tell us what you want to achieve, because I think you could compose the observables in a way better way, than subscribing and posting onNext onto the subject.
Please have a look at my example. I use RxJava2 as environment.
public class ViewModelTest {
class ActionsEvent {
}
class ActionFetchedEvent extends ActionsEvent {
public ActionFetchedEvent(ActionsEvent actionEvent) {
}
}
interface Service {
public Observable<ActionsEvent> getActions();
}
class MyViewModel {
private BehaviorSubject<ActionsEvent> binder;
private Service service;
public MyViewModel(Service service) {
this.service = service;
this.binder = BehaviorSubject.create();
}
public Observable<ActionsEvent> getBinder() {
return binder.doOnSubscribe(disposable -> {
service.getActions().subscribe(action -> {
binder.onNext(new ActionFetchedEvent(action));
}
);
});
}
}
#Test
public void name() throws Exception {
Service mock = mock(Service.class);
MyViewModel viewModel = new MyViewModel(mock);
when(mock.getActions()).thenAnswer(invocation -> {
return Observable.just(new ActionsEvent());
});
TestObserver<ActionsEvent> test = viewModel.getBinder().test();
test.assertValueCount(1);
}
}

RxJava+Retrofit 2 unit test weird error

I am creating Android application using MVP pattern.
For that I am using Retrofit 2 and RxJava. App works fine
But in unit testing I am getting weird error.Same test code sometimes passes, sometimes fails.
Error displays with this message
Wanted but not invoked:
albumView.showProgress();
-> at kz.afckairat.kairat.media.AlbumPresenterTest.checkGetPhotoAlbums(AlbumPresenterTest.java:66)
Actually, there were zero interactions with this mock.
Test class
public class AlbumPresenterTest {
enter code here
private MediaService mediaService;
private AlbumView albumView;
private AlbumPresenterImpl photoAlbumPresenter;
#Before
public void setUp() throws Exception {
albumView = mock(AlbumView.class);
mediaService = mock(MediaService.class);
photoAlbumPresenter = new AlbumPresenterImpl(albumView, mediaService, MediaType.PHOTO);
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
#Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
});
}
#After
public void tearDown() {
RxAndroidPlugins.getInstance().reset();
}
#Test
public void checkGetPhotoAlbums() {
List<Album> albums = getAlbumList();
when(mediaService.getPhotoAlbums()).thenReturn(Observable.just(albums));
photoAlbumPresenter.getAlbums();
verify(albumView).showProgress();
verify(albumView).showAlbums(albums);
verify(albumView).hideProgress();
}
#Test
public void checkGetPhotoAlbumError() {
String msg = "Error";
when(mediaService.getPhotoAlbums()).thenReturn(Observable.error(new IOException(msg)));
photoAlbumPresenter.getAlbums();
verify(albumView).showProgress();
verify(albumView).showError(msg);
verify(albumView).hideProgress();
}
private List<Album> getAlbumList() {
List<Album> albums = new ArrayList<>();
Album album = new Album(1, "Test1", "test1.jpg", "01.01.2016", 2);
albums.add(album);
album = new Album(2, "Test2", "test2.jpg", "01.01.2016", 2);
albums.add(album);
return albums;
}
}
Presenter class which is tested
public class AlbumPresenterImpl implements AlbumPresenter {
private AlbumView view;
private MediaType type;
private List<Album> albums;
private MediaService mediaService;
public AlbumPresenterImpl(AlbumView view, MediaService mediaService, MediaType type) {
this.view = view;
this.mediaService = mediaService;
this.type = type;
}
#Override
public void getAlbums() {
Observable<List<Album>> observable;
if (type.equals(MediaType.VIDEO)) {
observable = mediaService.getVideoAlbums();
} else {
observable = mediaService.getPhotoAlbums();
}
observable.doOnSubscribe(view::showProgress)
.doAfterTerminate(view::hideProgress)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(items -> {
albums = items;
view.showAlbums(albums);
}, throwable -> {
view.showError(throwable.getLocalizedMessage());
});
}
#Override
public void onResume() {
if (albums == null) {
getAlbums();
}
}
#Override
public void onDestroy() {
}
}
Why sometimes test don't pass?
Thanks a lot!
=================================
Update
As #Fred wrote problem was in Schedulers
public class RxSchedulersOverrideRule implements TestRule {
private final RxJavaSchedulersHook mRxJavaSchedulersHook = new RxJavaSchedulersHook() {
#Override
public Scheduler getIOScheduler() {
return Schedulers.immediate();
}
#Override
public Scheduler getNewThreadScheduler() {
return Schedulers.immediate();
}
};
private final RxAndroidSchedulersHook mRxAndroidSchedulersHook = new RxAndroidSchedulersHook() {
#Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
};
// Hack to get around RxJavaPlugins.reset() not being public
// See https://github.com/ReactiveX/RxJava/issues/2297
// Hopefully the method will be public in new releases of RxAndroid and we can remove the hack.
private void callResetViaReflectionIn(RxJavaPlugins rxJavaPlugins)
throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
Method method = rxJavaPlugins.getClass().getDeclaredMethod("reset");
method.setAccessible(true);
method.invoke(rxJavaPlugins);
}
#Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
RxAndroidPlugins.getInstance().reset();
RxAndroidPlugins.getInstance().registerSchedulersHook(mRxAndroidSchedulersHook);
callResetViaReflectionIn(RxJavaPlugins.getInstance());
RxJavaPlugins.getInstance().registerSchedulersHook(mRxJavaSchedulersHook);
base.evaluate();
RxAndroidPlugins.getInstance().reset();
callResetViaReflectionIn(RxJavaPlugins.getInstance());
}
};
}
}
Code taken from Github a link!
And in Test class
#Rule
public final RxSchedulersOverrideRule mOverrideSchedulersRule = new RxSchedulersOverrideRule();
It seems you override the main thread scheduler with:
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
#Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
});
But from the code, the observables still run on the Schedulers.io() scheduler:
observable.doOnSubscribe(view::showProgress)
.doAfterTerminate(view::hideProgress)
.subscribeOn(Schedulers.io())
// ...
As you may know, the immediate scheduler executes code in the current thread, which I guess since you jump to the io scheduler it's a different one from the one the tests run on.
This will make the test run in one thread and the subscribers/observables in another. This would explain why sometimes the tests pass and sometimes they don't. There's a race condition.
Essential the easiest way is to make sure that at test time you have both observeOn and subscribeOn on Schedulers.immediate() and at run time you have the correct ones, i.e., Schedulers.io() and AndroidSchedulers.mainThread().
You can do this by overriding the schedulers, by passing them as constructors or you could even take a look at this where Dan Lew explains how to use compose to create scheduler transformers. You can then make sure your classes at run time use a proper scheduler transformer and at test time they use some transformer that puts everything on the immediate thread.

RxJava subscriber as a parameter does not work, new identical subscriber work

We're using rxandroid:1.1.0 along with rxjava:1.1.2 in our application.
Our project has the RxJava Subscriber created this way:
Android Activity creates the Subscriber. When user deletes an item we pass the Subscriber as argument to a "network service" class we have
The network service class sets up the Observable, and gets the Subscriber as argument. The Observable is set to subscribe to the Subscriber argument
This unfortunately doesn't work. The subscriber never gets called and we don't get errors or any more information.
However, if instead of using the argument Subscriber, and I create a brand new Subscriber of the same time, the Observable works just fine.
Any idea how I could make this work with the argument Subscriber? We want to call UI methods onComplete from the Activity that are not accessible from the network service class.
Here's my activity code:
public Subscriber<Models.FavoriteResponse> mFavoritesSubscriber = new Subscriber<Models.FavoriteResponse>() {
#Override
public void onCompleted() {
// empty
Logger.w("mFavoritesSubscriber onCompleted");
showFavorites();
}
#Override
public void onError(Throwable e) {
Logger.w("mFavoritesSubscriber onError");
}
#Override
public void onNext(Models.FavoriteResponse favoriteResponse) {
Logger.w("mFavoritesSubscriber onNext");
getHelper().setIsFavoriteOnProducts(favoriteResponse.getFavoriteResponseList());
}
};
private void removeSelectedFavorites() {
for (Product selectedProduct : selectedProducts) {
ServiceUtils.removeFavorite(this, selectedProduct.sku, mFavoritesSubscriber);
}
}
And here's the code from my network service class.
This version does not work:
public static void removeFavorite(final Context context, final String sku, Subscriber<Models.FavoriteResponse> subscriber) {
Logger.w("ServiceUtils removeFavorite");
String userId = PersistentSharedPreferences.readUserId(context);
FavoritesServices service = FavoritesServices.Companion.create(BuildConfig.ORDER_SERVICE_DOMAIN,
new Interceptor(context));
final Observable<Models.FavoriteResponse> observable = service
.deleteFavorite(userId, sku, getFavoritesParameters(context));
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.onErrorResumeNext(new Func1<Throwable, Observable<? extends Models.FavoriteResponse>>() {
#Override
public Observable<? extends Models.FavoriteResponse> call(Throwable throwable) {
Logger.w("ServiceUtils onErrorResumeNext");
return null;
}
})
.subscribe(subscriber);
}
But this version of the network service class method works fine. Notice the new Subscriber:
public static void removeFavorite(final Context context, final String sku, Subscriber<Models.FavoriteResponse> subscriber) {
Logger.w("ServiceUtils removeFavorite");
String userId = PersistentSharedPreferences.readUserId(context);
FavoritesServices service = FavoritesServices.Companion.create(BuildConfig.ORDER_SERVICE_DOMAIN,
new Interceptor(context));
final Observable<Models.FavoriteResponse> observable = service
.deleteFavorite(userId, sku, getFavoritesParameters(context));
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.onErrorResumeNext(new Func1<Throwable, Observable<? extends Models.FavoriteResponse>>() {
#Override
public Observable<? extends Models.FavoriteResponse> call(Throwable throwable) {
Logger.w("ServiceUtils onErrorResumeNext");
return null;
}
})
.subscribe(new Subscriber<Models.FavoriteResponse>() {
#Override
public void onCompleted() {
Logger.w("ServiceUtils Subscriber onCompleted");
}
#Override
public void onError(Throwable e) {
Logger.w("ServiceUtils Subscriber onError");
}
#Override
public void onNext(Models.FavoriteResponse favoriteResponse) {
Logger.w("ServiceUtils Subscriber onNext");
}
});
}
Any help would be appreciated. Thank you!
Don't reuse a Subscriber. Subscriber is a Subscription and it's stateful. Subscriber won't be notified after it's unsubscribed.
You can use Observer instead.

How to determine if a given request is running?

I'm looking at retrofit for my networking layer. Is there any way to tell if a particular async request is running at any given moment?
For example, I'd like to know if a request is running so that I can update the user interface at various times. I could do this myself by keeping variables around to track state, but wondering if there's something already in the library for this.
Here is what I would normally do when needing a way to keep track of running requests:
First, using retrofit, as soon as you make the request, you can do the following:
Use EventBus library to post an event to your activity or fragment. Now, this can be done inside onSuccess() method of your Callback or onError() method of the same.
In your activity or fragment's onEvent(EventClassName event) method, you can simply check a variable like [isRunning] from your event to make sure that if the event is still running, you update the UI accordingly and if not, do what you need to do respectively.
When the request is completed, obviously isRunning will be false and you can then update the UI as expected by the user.
I am recommending EventBus here simply because it is much easier to decouple your application code with it; you can send different events that notify the activity of the different statuses of your requests and then update your UI that way.
You can find EventBus here
I hope this helps!
What I personally ended up doing in this case was that I was running the example with Retrofit, Android Priority Jobqueue (from yigit's fork) and Otto eventbus.
public enum SingletonBus {
INSTANCE;
private Bus bus;
private Handler handler = new Handler(Looper.getMainLooper());
private SingletonBus() {
this.bus = new Bus(ThreadEnforcer.ANY);
}
public <T> void postToSameThread(final T event) {
bus.post(event);
}
public <T> void postToMainThread(final T event) {
handler.post(new Runnable() {
#Override
public void run() {
bus.post(event);
}
});
}
public <T> void register(T subscriber) {
bus.register(subscriber);
}
public <T> void unregister(T subscriber) {
bus.unregister(subscriber);
}
}
public interface Interactor {
void injectWith(PresenterComponent presenterComponent);
}
public interface SendCertificateRequestInteractor
extends Interactor {
interface Listener {
void onSuccessfulEvent(SuccessfulEvent e);
void onFailureEvent(FailureEvent e);
}
class SuccessfulEvent
extends EventResult<CertificateBO> {
public SuccessfulEvent(CertificateBO certificateBO) {
super(certificateBO);
}
}
class FailureEvent
extends EventResult<Throwable> {
public FailureEvent(Throwable throwable) {
super(throwable);
}
}
void sendCertificateRequest(String username, String password);
}
Pay attention to the Job here:
public class SendCertificateRequestInteractorImpl
implements SendCertificateRequestInteractor {
private Presenter presenter;
private boolean isInjected = false;
#Inject
public JobManager jobManager;
public SendCertificateRequestInteractorImpl(Presenter presenter) {
this.presenter = presenter;
}
#Override
public void sendCertificateRequest(String username, String password) {
if(!isInjected) {
injectWith(presenter.getPresenterComponent());
isInjected = true;
}
InteractorJob interactorJob = new InteractorJob(presenter, username, password);
long jobId = jobManager.addJob(interactorJob); //this is where you can get your jobId for querying the status of the task if you want
}
#Override
public void injectWith(PresenterComponent presenterComponent) {
presenterComponent.inject(this);
}
public static class InteractorJob
extends Job {
private final static int PRIORITY = 1;
private final static String TAG = InteractorJob.class.getSimpleName();
private String username;
private String password;
#Inject
public MyService myService;
public InteractorJob(Presenter presenter, String username, String password) {
super(new Params(PRIORITY).requireNetwork());
presenter.getPresenterComponent().inject(this);
this.username = username;
this.password = password;
}
#Override
public void onAdded() {
// Job has been saved to disk.
// This is a good place to dispatch a UI event to indicate the job will eventually run.
// In this example, it would be good to update the UI with the newly posted tweet.
}
#Override
public void onRun()
throws Throwable {
String certificate = myService.getCertificate(username, password);
SingletonBus.INSTANCE.postToMainThread(new SuccessfulEvent(certificate));
}
#Override
protected void onCancel() {
// Job has exceeded retry attempts or shouldReRunOnThrowable() has returned false.
Log.e(TAG, "Cancelled job.");
}
#Override
protected boolean shouldReRunOnThrowable(Throwable throwable) {
// An error occurred in onRun.
// Return value determines whether this job should retry running (true) or abort (false).
Log.e(TAG, "Failed to execute job.", throwable);
SingletonBus.INSTANCE.postToMainThread(new FailureEvent(throwable));
return false;
}
}
}
And then
#Subscribe
#Override
public void onSuccessfulEvent(SendCertificateRequestInteractor.SuccessfulEvent e) {
String certificate = e.getResult();
//do things
}
#Subscribe
#Override
public void onFailureEvent(SendCertificateRequestInteractor.FailureEvent e) {
Throwable throwable = e.getResult();
//handle error
}
More about android priority jobqueue here.
This way, technically the async handling is referred to the job queue, while Retrofit itself is using the synchronous interface. It works well as long as you don't need to access the headers of the response. Although to be fair, I was also keeping track of whether the job was running with a boolean instead of the job manager and the id as well..
Also, I haven't figured out how to use dependency injection properly with persisted jobs; nor do I really know how they intended to make that work. Of course, it'd work if it was using the application scoped component rather than a supplied presenter scoped one, but that is irrelevant.
You'll probably need to customize this solution to your own scenario, and use only what you actually need.

Categories

Resources