How to clear rxJava disposables in android mvvm with repository design? - android

I am new to MVVM and trying to clear my rxJava disposables, i have seen some answers saying to clear it in ViewModel in onClear method but how do i get to add the disposable in the first place ?
//Repository Code
public class MyRepository {
public MutableLiveData<String> deleteDraftById(int recordId {
final MutableLiveData<String> result = new MutableLiveData<>();
Completable deleteDraftById = completedDao.deleteDraftById(recordId);
deleteDraftById.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
result.setValue("1");
}
#Override
public void onError(Throwable e) {
result.setValue(e.getMessage());
}
});
return result;
}
}
//ViewModel
public class MyViewModel extends AndroidViewModel {
public MutableLiveData<String> deleteDraftById(int recordId){
return myRepository.deleteDraftById(recordId);
}
}

In my opinion nothing wrong with using live data in repos, for example if single source of truth is needed. Here is what I'd suggested (rxjava 1.x assumed, pseudocode a-la java) :
public class MyRepository {
public final MutableLiveData<String> result = new MutableLiveData<>();
public Completable deleteDraftById(int recordId) {
return completedDao.deleteDraftById(recordId)
.doOnSubscribe(...) //potentially report progress start, if needed
.doOnSuccess(...) //report success to your live data aka result.value = ...
.onErrorComplete(...) //report error to your live data and complete
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
}
public class MyViewModel(....pass MyRepository) extends AndroidViewModel {
//expose live data from repo somehow, may be like this:
public final LiveData<String> abc = myRepository.result;
private final CompositeSubscription compositeSubscription = new CompositeSubscription();
//call this from ui
public void delete(int recordId) {
compositeSubscription.add(
myRepository
.deleteDraftById(recordId)
.subscribe()
)
}
#Override
protected void onCleared() {
super.onCleared();
compositeSubscription.clear();
}
}

Related

Android LiveData is always null in instrumented test

In my app, I have a ViewModel looks like that:
public class MyExampleViewModel {
private LiveData<MyEntity> myLiveData;
#Inject
MyRepository myRepository;
#Inject
public MyExampleViewModel() {
}
public void init(final Long id) {
if (this.myLiveData == null) {
this.myLiveData = myRepository.getById(id);
}
}
public void toggleStar() NullPointerException {
final MyEntity myValue = this.myLiveData.getValue();
myValue.setStar(!myValue.getStar());
myRepository.save(myValue);
}
}
Also the code of MyRepository#getById (myDao is a room DAO and it is injected):
public LiveData<MyEntity> getById(final Long id) {
return myDao.getById(id);
}
The code of MyDao#getById:
#Query(
"SELECT * FROM myTable WHERE id=:id"
)
LiveData<MyEntity> getById(final Long id);
I also try to test this ViewModel using
myExampleViewModel.init(myId);
myExampleViewModel.toggleStar();
but after the init call my LiveData value is always null.
My first question is: is it a best practice to use getValue() on my LiveData or should I use Transformation.map?
My second question is: in my test, how can I have a LiveData populated? I tried to use CountingTaskExecutorRule and InstantTaskExecutorRule but without any success.
Thank you for your help!
I understood why myLiveData is not populated in my test. According to the documentation "LiveData objects that are lazily calculated on demand." and LiveData#getValue only get the value if the LiveData is already populated but doesn't calculate the value.
So I fixed my test adding a getter on my LiveData and an observer on my LiveData to force the calculation like that LiveDataUtil.getValue(myExampleViewModel.getMyLiveData()); with LiveDataUtil#getValue:
public class LiveDataUtil {
public static <T> T getValue(final LiveData<T> liveData) throws InterruptedException {
final Object[] data = new Object[1];
final CountDownLatch latch = new CountDownLatch(1);
Observer<T> observer = new Observer<T>() {
#Override
public void onChanged(#Nullable T o) {
data[0] = o;
latch.countDown();
liveData.removeObserver(this);
}
};
new Handler(Looper.getMainLooper()).post(() -> liveData.observeForever(observer));
latch.await(2, TimeUnit.SECONDS);
//noinspection unchecked
return (T) data[0];
}
}
After this fix, MyExampleViewModel class looks like:
public class MyExampleViewModel {
private LiveData<MyEntity> myLiveData;
#Inject
MyRepository myRepository;
#Inject
public MyExampleViewModel() {
}
public void init(final Long id) {
if (this.myLiveData == null) {
this.myLiveData = myRepository.getById(id);
}
}
public void toggleStar() NullPointerException {
final MyEntity myValue = this.myLiveData.getValue();
myValue.setStar(!myValue.getStar());
myRepository.save(myValue);
}
public LiveData<MyEntity> getMyLiveData() {
return myLiveData;
}
}
And my test method:
myExampleViewModel.init(myId);
LiveDataUtil.getValue(myExampleViewModel.getMyLiveData());
myExampleViewModel.toggleStar();
I fixed my test but I still don't know if using LiveData.getValue is a best practice and I found few documentation on this topic. So, I'm interested in this topic if you have more information.

MVVM architecture problem when trying to get my item details?

Here I am not asking for code problem, but just architecture, I will show what Iof course.
So I was just playing around with this MVVM architecture, I was displaying a list of items, I click an item I see its details, there are many categories in those details, meaning that for example I had a personal data displayed in one cardview, informations about his credit card for example in an other cardview, etc.. For some Users who are connected, credit cards data must not be displayed, but when I created the view model in the begging it was like this :
public class ContractViewModel extends ViewModel {
public MutableLiveData<ContractModel> contract = new MutableLiveData<ContractModel>();
public MutableLiveData<Boolean> isLoading = new MutableLiveData<Boolean>();
public MutableLiveData<Boolean> error = new MutableLiveData<Boolean>();
#Inject
ContractService contractService;
CompositeDisposable disposable = new CompositeDisposable();
public ContractViewModel(){
DaggerContractApiComponent.create().inject(this);
}
public void call(SingleContractRequest singleContractRequest){
fetchContract(singleContractRequest);
}
public void fetchContract(SingleContractRequest singleContractRequest){
isLoading.setValue(true);
disposable.add(
contractService.getContract(singleContractRequest)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<ContractModel>() {
#Override
public void onSuccess(ContractModel contractModel) {
isLoading.setValue(false);
error.setValue(false);
contract.setValue(contractModel);
}
#Override
public void onError(Throwable e) {
error.setValue(true);
isLoading.setValue(false);
e.printStackTrace();
}
})
);
}
#Override
protected void onCleared(){
super.onCleared();
disposable.clear();
}
}
But now I need the user connected role in the app to display the possible data that he can consult. The first thought coming to my head was just calling the service that return my userConnected.
public class ContractViewModel extends ViewModel {
public MutableLiveData<ContractModel> contract = new MutableLiveData<ContractModel>();
public MutableLiveData<UserConnected> userConnected = new MutableLiveData<UserConnected>();
public MutableLiveData<Boolean> isLoading = new MutableLiveData<Boolean>();
public MutableLiveData<Boolean> error = new MutableLiveData<Boolean>();
#Inject
ContractService contractService;
#Inject
UserConnectedService userConnectedService;
CompositeDisposable disposable = new CompositeDisposable();
public ContractViewModel(){
DaggerContractApiComponent.create().inject(this);
DaggerUserConnectedApiComponent.create().inject(this);
}
public void call(SingleContractRequest singleContractRequest){
fetchContract(singleContractRequest);
}
public void getUserConnected(){
disposable.add(
userConnectedService.getUserConnected()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<UserConnected>() {
#Override
public void onSuccess(UserConnected userConnectedParams) {
userConnected.setValue(userConnectedParams);
}
#Override
public void onError(Throwable e) {
}
})
);
}
public void fetchContract(SingleContractRequest singleContractRequest){
isLoading.setValue(true);
disposable.add(
contractService.getContract(singleContractRequest)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<ContractModel>() {
#Override
public void onSuccess(ContractModel contractModel) {
isLoading.setValue(false);
error.setValue(false);
contract.setValue(contractModel);
}
#Override
public void onError(Throwable e) {
error.setValue(true);
isLoading.setValue(false);
e.printStackTrace();
}
})
);
}
#Override
protected void onCleared(){
super.onCleared();
disposable.clear();
}
}
But it is not clean, I have to call two methods at the same time in my Activity, which is terrible, the second thought is that I have to edit my contract type from MutableLiveData<ContractModel> to something like this MutableLiveData<NewModel> knowing that this NewModel architecture is like this :
public NewModel {
ContractModel contractModel;
UserModel userModel;
// ofc here a constuctor and getters and setters
}
But this has a problem too that will be caused in the backend, because what if I need the userConnected in an other activity ? I will have to write similar code in multiple places to get this userConnected, but I want to get it once.
Any help, thoughts would be really helpful.

Android ViewModel with custom Model

I'm planning to have a model class and provide an instance of this model through an Android ViewModel. The instance in the ViewModel can change through the application lifecycle.
My current idea is like this:
public class Book {
private MutableLiveData<String> mName = new MutableLiveData<>();
public Book(...) {
...
}
public LiveData<String> getName() {
return mName;
}
public void setName(String name) {
mName.setValue(name);
}
}
public class MyViewModel extends ViewModel {
private MutableLiveData<Book> mCurrentBook = new MutableLiveData<>();
private MutableLiveData<Book> mRecommendedBook = new MutableLiveData<>();
public LiveData<Book> getCurrentBook() {
return mCurrentBook;
}
public void setCurrentBook(Book book) {
mCurrentBook.setValue(book);
}
}
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getCurrentBook().observe(this, book -> {
book.getName().observe(this, name -> {
// update UI
});
});
...
model.setCurrentBook(someOtherBook);
}
}
Is this a good approach? I'm not sure if it's a good idea to have the LiveData nested in another class.
Also could it be a problem that I'm creating a new observer for the book name, each time the book changes?
I answered a similar question here
You should use Transformation to carry data between your observer's.

Testing Presenter with DisposableObserver

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

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

Categories

Resources