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);
}
}
Related
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>'
I'm developing an Android application, trying to follow Clean Architecture / MVP guidelines.
I'm currently wiriting Unit Tests for my presenters, but I'm stuck with the call to the Interactor/UseCase, that takes a DisposableObserver as a parameter.
What I would like to test is that the correct behavior is called when the interactor invokes OnNext or OnError for example (hide/show loading indicator...).
I don't know how to 'mock' the behavior of the Observable in my use Case, as it is built when the execute() method is called, using a protected method.
Below are some portions of code:
Presenter
#ConfigPersistent
public class ContentPresenter extends BasePresenter<ContentContract.View> implements ContentContract.Presenter {
#Inject
GetContent mGetContentUseCase;
#Inject
ContentViewModelMapper mContentViewModelMapper;
#Inject
public ContentPresenter() {
}
#Override
public void fetchContent(long contentId) {
getMvpView().showProgress();
mGetContentUseCase.execute(contentId, new ContentObserver());
}
private final class ContentObserver extends DisposableObserver<Content> {
#Override
public void onNext(Content content) {
getMvpView().hideProgress();
getMvpView().showContentInfo(mContentViewModelMapper.map2(content));
}
#Override
public void onError(Throwable e) {
getMvpView().hideProgress();
Timber.e(e.getMessage());
}
#Override
public void onComplete() {
getMvpView().hideProgress();
}
}
}
Interactor/UseCase
public class GetContent extends UseCaseObservableWithParameter<Long, Content, Repository> {
#Inject
public GetContent(Repository repository,
#Named("Thread") Scheduler threadScheduler,
#Named("PostExecution") Scheduler postExecutionScheduler) {
super(repository, threadScheduler, postExecutionScheduler);
}
#Override
protected Observable<Content> buildObservable(Long id) {
return repository.getContentById(id);
}
}
BaseUseCase
public abstract class UseCaseObservableWithParameter<REQUEST_DATA, RESPONSE_DATA, REPOSITORY> extends UseCase<Observable<RESPONSE_DATA>, REQUEST_DATA, REPOSITORY> {
public UseCaseObservableWithParameter(REPOSITORY repository, Scheduler threadScheduler, Scheduler postExecutionScheduler) {
super(repository, threadScheduler, postExecutionScheduler);
}
protected abstract Observable<RESPONSE_DATA> buildObservable(REQUEST_DATA requestData);
public void execute(REQUEST_DATA requestData, DisposableObserver<RESPONSE_DATA> useCaseSubscriber) {
this.disposable.clear();
this.disposable.add(
this.buildObservable(requestData)
.subscribeOn(threadScheduler)
.observeOn(postExecutionScheduler)
.subscribeWith(useCaseSubscriber)
);
}
}
After a good night's sleep, this is what I came up with:
#Test
#SuppressWarnings("unchecked")
public void testShowContents() {
doAnswer((i) -> {
// Do stuff with i.getArguments() here
DisposableObserver<Content> d = i.getArgument(1);
Observable.just(mock(Content.class)).subscribeWith(d);
return null;
})
.when(mGetContentUseCase)
.execute(eq(AppTestData.TEST_LONG_ID_1), any(DisposableObserver.class));
contentPresenter.fetchContent(AppTestData.TEST_LONG_ID_1);
Mockito.verify(view, Mockito.times(1)).showContentInfo(Mockito.any());
InOrder orderVerifier = Mockito.inOrder(view);
orderVerifier.verify(view).showProgress();
orderVerifier.verify(view).hideProgress();
orderVerifier.verify(view).showContentInfo(any());
}
#Test
#SuppressWarnings("unchecked")
public void testShowContentsError() {
doAnswer((i) -> {
// Do stuff with i.getArguments() here
DisposableObserver<Content> d = i.getArgument(1);
Observable.<Content>error(new Throwable()).subscribeWith(d);
return null;
})
.when(mGetContentUseCase)
.execute(eq(AppTestData.TEST_LONG_ID_1), any(DisposableObserver.class));
contentPresenter.fetchContent(AppTestData.TEST_LONG_ID_1);
Mockito.verify(view, Mockito.times(1)).showErrorMessage(Mockito.any());
InOrder orderVerifier = Mockito.inOrder(view);
orderVerifier.verify(view).showProgress();
orderVerifier.verify(view).hideProgress();
orderVerifier.verify(view).showErrorMessage(any());
}
I've never create unit testing before. I'm planning to create UI test & Unit test for my presenter & datasource. I use Retrofit, RxJava, and Dagger in my apps.
Here's what i've tried so far
DataSource (My Datasource is coming from API)
public class DataSource implements DataSourceContract {
private static DataSource dataSource;
#Inject
SharedPreferences sharedPreferences;
#Inject
NewsService newsService;
private DataSource(Context context) {
DaggerAppComponent.builder()
.networkModule(new NetworkModule(API_URL))
.appModule(new AppModule(context.getApplicationContext()))
.preferencesModule(new PreferencesModule())
.build()
.inject(this);
}
public static synchronized DataSource getInstance(Context context) {
if(dataSource == null) {
dataSource = new DataSource(context);
}
return dataSource;
}
public String parseError(Throwable e) {
if(e instanceof SocketTimeoutException) {
return ERROR_TIMEOUT;
}
else if(e instanceof SocketException) {
return ERROR_NO_CONNECTION;
}
else {
return ERROR_SERVER;
}
}
#Override
public DisposableObserver<NewsResponse> getNews(final Callback<NewsResponse> callback) {
return newsService.getNews()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<NewsResponse>() {
#Override
public void onNext(NewsResponse value) {
callback.onSuccess(value);
}
#Override
public void onError(Throwable e) {
callback.onFailure(e);
}
#Override
public void onComplete() {
}
});
}
}
Presenter
public class MainPresenter implements MainContract.Presenter {
private MainContract.View view;
private DataSource dataSource;
private Disposable dispossable;
public MainPresenter(MainContract.View view, DataSource dataSource) {
this.view = view;
this.dataSource = dataSource;
}
#Override
public void onStart() {
getNews();
}
#Override
public void onStop() {
if(dispossable != null && !dispossable.isDisposed()) {
dispossable.dispose();
}
}
#Override
public void getNews() {
view.setLoading(true);
dispossable = dataSource.getNews(new DataSourceContract.Callback<NewsResponse>() {
#Override
public void onSuccess(NewsResponse responseData) {
try {
switch (responseData.getStatus()) {
case API_SUCCESS:
view.setLoading(false);
view.getNewsSuccess(responseData.getArticles());
break;
default:
view.setLoading(false);
view.getNewsFailed(responseData.getStatus());
break;
}
}
catch (Exception e) {
view.setLoading(false);
view.getNewsFailed(ERROR_SERVER);
}
}
#Override
public void onFailure(Throwable e) {
view.setLoading(false);
view.isNetworkFailed(dataSource.parseError(e), false);
}
});
}
}
And this is the test of my presenter
public class MainPresenterTest {
#Mock
DataSource dataSource;
#Mock
MainContract.View view;
MainContract.Presenter presenter;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
presenter = new MainPresenter(view, dataSource);
}
#Test
public void getNews() throws Exception {
List<Article> articleList = new ArrayList<>();
presenter.getNews();
Mockito.verify(view, Mockito.only()).getNewsSuccess(articleList);
}
}
But there is error when I run the test
Wanted but not invoked:
view.getNewsSuccess([]);
-> at com.java.mvp.view.main.MainPresenterTest.getNews(MainPresenterTest.java:37)
I have no problem running this apps on the device, but I can't make it work on testing
Any idea how to fix this presenter test? Am I doing it right?
And how do I test my datasource? I have no idea how to test this one
Thank you
Keep things simple. You are testing your presenter, not the data source. Add new methods to your presenter for the success and error responses. Then add two tests: one for the success and one for the error.
#Override
public void getNews() {
view.setLoading(true);
dispossable = dataSource.getNews(new DataSourceContract.Callback<NewsResponse>() {
#Override
public void onSuccess(NewsResponse responseData) {
onSuccessNewsResponse(responseData);
}
#Override
public void onFailure(Throwable e) {
onErrorNewsResponse(e);
}
});
}
Add #VisibleForTesting annotation to the new methods.
Success test:
#Test
public void getNewsSuccess() {
presenter.onSuccessNewsResponse(your_response);
Mockito.verify(...);
}
Error test:
#Test
public void getNewsError() {
presenter.onErrorNewsResponse(your_error);
Mockito.verify(...);
}
You have to mock also :
dataSource.getNews() using Mockito when :
e.g.
when(dataSource.getNews()).thenReturn(new SuccessCallback());
So you have to lead your test code into the success callback and check there what methods are called.
The same goes with the eroor case.
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.
I am trying to send an update to my Activity from my GCMServiceListener so, I am using RxJava/RxAndroid And created a BusClass for handling sending and Observers
public class ClientBus {
//private final PublishSubject<Object> _bus = PublishSubject.create();
// If multiple threads are going to emit events to this
// then it must be made thread-safe like this instead
private final Subject<Object, Object> _bus = new SerializedSubject<>(PublishSubject.create());
public void send(Object o) {
_bus.onNext(o);
}
public Observable<Object> toObserverable() {
return _bus;
}
public boolean hasObservers() {
return _bus.hasObservers();
}
}
And in my Application Class I did this to initialize the BusClass
private ClientBus clientBus;
public ClientBus getRxBusSingleton() {
if (clientBus == null) {
clientBus = new ClientBus();
}
return clientBus;
}
In the activity I want to receive the message, I registered a CompositeSubscription and get a reference to my ClientBus class from the Application Class
clientBus = ((MyApplication) getApplicationContext()).getRxBusSingleton();
#Override
protected void onStart() {
super.onStart();
initSubscriptions();
}
#Override
protected void onStop() {
super.onStop();
_subscriptions.unsubscribe();
}
void initSubscriptions() {
_subscriptions = new CompositeSubscription();
_subscriptions.add(clientBus.toObserverable().subscribe(new Action1<Object>() {
#Override
public void call(Object event) {
Log.e("New Event", "Event Received");
if (event instanceof MyGcmListenerService.Message) {
String msg = ((MyGcmListenerService.Message) event).getMessage();
if (msg.equals("Update Available")) {
scheduleArrayList = getSchedules();
scheduleAdapter = new ScheduleAdapter(getApplicationContext(), scheduleArrayList, ScheduledUberActivity.this);
scheduledList.setAdapter(scheduleAdapter);
scheduleAdapter.notifyDataSetChanged();
} else if (msg.equals("Refresh")) {
fetchTrips();
}
}
}
}));
}
And from the MyGcmListenerService class I did this when I get a new notification
private void sendRefreshNotif() {
if (clientBus.hasObservers()) {<--It enters the if cause the Log prints. But, the activity doesn't get the message
Log.e("Obervers", "Observers aren't null");
clientBus.send(new Message("Refresh"));
}
}
What I don't understand is why isn't it working here? I use it to interact between activities and fragments. I closed my application to check if the notification comes in, It'll enter this block if (clientBus.hasObservers()) { but it didn't and starting the app and testing the Observer, it notices there's an active Observer. Any help? Thanks.
It seems like you used different instances of the ClientBus class in CompositeSubscription and MyApplication.
Try to make a singleton from ClientBus class, it works fine for me.
public class ClientBus {
public ClientBus(SingletonAccessor accessor) {}
private static ClientBus instance;
private static class SingletonAccessor{}
public static ClientBus getInstance() {
if (instance == null) instance = new ClientBus(new SingletonAccessor());
return instance;
}
private final Subject<Object, Object> mBus = new SerializedSubject<>(PublishSubject.create());
public void send(Object o) {
mBus.onNext(o);
}
public Observable<Object> toObserverable() {
return mBus;
}
public boolean hasObservers() {
return mBus.hasObservers();
}
}