What is the proper way to execute some kind of "background" action, like saving data in the database, when this action is triggered by a presenter, but the presenter is disposing the observable before the end of the query?
Should I decouple my Repository observable from the UseCase one?
Example
I'me developing an Android application following the Clean Architecture and MVP patterns, using RxJava and Dagger.
In a Dialog, I'm listing devices, and can connect to one when selecting it in the list.
When an element in the list is clicked, the dialog is dismissed, and I'm setting/saving this device as the 'current' device in my repository layer and animating a logo in the toolbar of the activity (somehow like the Chromecast is doing).
In my presenter, I dispose my UseCases when the view is detached, so if the saving is not complete when the dialog is dismissed, the observable gets disposed and the device is not set as 'current' device.
Presenter
#Override
public void deviceClicked(String id) {
getMvpView().dismissView();
mConnectToDeviceUseCase.execute(id, new DisposableCompletableObserver() {
#Override
public void onComplete() {
Timber.d("Connected to device");
}
#Override
public void onError(Throwable e) {
Timber.e("Error while connecting to device: %s", e.getMessage());
}
});
}
UseCase
public class ConnectToDeviceUseCase extends UseCaseCompletableWithParameter<String, DevicesRepository> {
#Inject
public ConnectToDeviceUseCase(DevicesRepository DevicesRepository,
#Named("Thread") Scheduler threadScheduler,
#Named("PostExecution") Scheduler postExecutionScheduler) {
super(devicesRepository, threadScheduler, postExecutionScheduler);
}
#Override
protected Completable buildObservable(String id) {
Timber.d("GetDevicesUseCase buildObservable");
return repository.connectToDevice(id);
}
}
Repository
private ReplaySubject<DeviceConnection> connectionStatus = ReplaySubject.create();
...
#Override
public Completable connectToDevice(String id) {
Timber.d("connectToDevice IN");
return mDLNADataSource.getDevices()
.flatMapIterable(items -> items)
.filter(item -> item.id().equals(id))
.firstOrError()
.flatMapCompletable(this::saveAsCurrent)
.doOnSubscribe(a ->
{
Timber.d("connectToDevice doOnSubscribe");
connectionStatus.onNext(DeviceConnection.builder().setStatus(DeviceConnection.STATUS_CONNECTING).build());
}
)
.doOnComplete(() ->
{
Timber.d("connectToDevice doOnComplete");
connectionStatus.onNext(DeviceConnection.builder().setStatus(DeviceConnection.STATUS_CONNECTED).build());
}
)
.doOnError(a ->
{
Timber.d("connectToDevice doOnError");
connectionStatus.onNext(DeviceConnection.builder().setStatus(DeviceConnection.STATUS_NOT_CONNECTED).build());
}
)
.doOnDispose(() ->
{
Timber.d("connectToDevice doOnDispose");
});
}
I am neither java nor Android expert but what you have described in your question sounds like controlflow issue to me.
If you trigger some async activity (e.g. save) you should also connect the cleanup to it. One possible solution could be using the concept of a "future" or "promise". Another one could be the actor pattern where save and cleanup would be messages executed sequentially.
Related
i'm working on project where i have to insert and delete data from room db , so basically i was using the old approach which is to implement Asynctask for background operations but since it is no longer recommended , i decided to use Rxjava instead , i tried to implement it but i'm not getting any result so far , and this is a piece of code where it shows the insertion of data
Completable.fromAction(new Action() {
#SuppressLint("CheckResult")
#Override
public void run() throws Exception {
recordingDb.insertRecording(modelUidd);
}
}).subscribeOn(Schedulers.io());
}
And this is the deletion method
public void DeleteData(modelUidd modelUidd) {
Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
recordingDb.delete(modelUidd);
}
}).subscribeOn(Schedulers.io());
}
So basically i tried to use completable with the operator fromaction , i'm not sure if what i implemented is correct or not , any help would appreciated guys , thank you
The problem is that you are actually not subscribing to the observables, so nothing is happening.
To subscribe to an observable, you have to call the .subscribe() method.
I suggest that your methods defined in your DAO classes (or you "repository" classes), such as DeleteData in your example, return the Observable. Then, you can call the method in the DAO to get the Observable and subscribe to it from (ideally) a ViewModel or, if not, directly from an Activity. The moment you call the subscribe you will trigger the actual insertion or deletion, and will get a response from the onSuccess or onError defined callbacks.
For example:
public class MyViewModel extends ViewModel {
private MyRepository myRepository;
private final CompositeDisposable disposables;
#Inject
public MyViewModel(MyRepository myRepository) {
...
this.myRepository = myRepository;
disposables = new CompositeDisposable();
...
}
public void callObservableInRepository() {
disposables.add(myRepository.myObservable()
.subscribe(onSuccess -> {...} , onError -> {...}));
}
#Override
protected void onCleared() {
disposables.clear();
}
}
You can also check these two other answers for more information:
About async operations in RxJava
Using CompositeDisposable in ViewModel
I'm just exploring Rxjava in one of my android application, and got stuck at one place, honestly speaking I'm very new to this library so don't mind if my question frustrate someone;-)
So I'm trying to access the Room Database using RxJava where I'm returning the Observable List, once I get this Observable I'm trying to use map operator to get a list of ids & query again the database, which again returns me the Observable List but the map operator expects List as a return type. How can I tackle this please suggest?
Below is the code snippet:
private void getAllPcbs() {
isLoading.setValue(true);
getCompositeDisposable().add(
getRepositoryManager().loadAllPcbDetails()
.flatMap((Function<List<PcbDetails>, ObservableSource<?>>) pcbDetails -> {
List<Long> pcbList = new ArrayList<>();
for (PcbDetails details : pcbDetails)
pcbList.add(details.getPcbId());
return getRepositoryManager().loadAllPcbs(pcbList);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onSuccess, this::onError)
);
}
private void onError(Throwable throwable) {
isLoading.setValue(false);
}
private void onSuccess(Object o) {
isLoading.setValue(false);
pcbList.setValue((List<Pcb>) o);
}
public interface DbHelper {
Observable<List<PcbDetails>> loadAllPcbDetails();
Observable<List<Pcb>> loadAllPcbs(List<Long> pcbIdList);
}
Go like
getRepositoryManager().loadAllPcbDetails()
.flatMapIterable {
listPcbDetail-> listPcbDetail
// listPcbDetail is ArrayList<PcbDetails>
// Converts your list of ids into an Observable
// which emits every item in the list
}
.flatMap { pcbDetail ->
// pcbDetail is PcbDetails
getRepositoryManager().loadAllPcbs(pcbDetail.pcbIdList)
}.subscribe { listPcb ->
// listPcb is ArrayList<Pcb>
}
Here's flow data in my app:
In view I got method onClick were I call presenter.Method(). In this method on presenter I pass the call to model(Model got his own layer of abstracion -> interface modelHelper. It's getting injected via dagger 2 in Conctructor Presenter).
In Model i got method for Network call :
#Override
public void networkCallForData(String request) {
request = "volumes?q=" + request;
compositeDisposable.add(
api.getBook(request)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
books -> {
items.clear();
items.addAll(books.items);
}
, Throwable::printStackTrace
, () -> {
}
)
);
}
}
I got 2 questions :
1. In MVP architecture should Model layer got injected instance of abstracted presenter and connect it to model just like with view ? if not how should i send data from model to presenter ?
I try connect presenter to model via RxJava2 but got problem with synchronization. In model i create observable from :
private List<Items> items = new ArrayList<>();
and getter method to it :
public Observable<List<Items>> getItemsObservable() {
return itemsObservable;
}
here i create observable :
private Observable<List<Items>> itemsObservable = Observable.fromArray(items);
In presenter i got :
private void getDataFromModel() {
compositeDisposable.add(
findActivityModel.getItemsObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
books -> {
view.setRecycler(books);
}, Throwable::printStackTrace
, () -> {
view.setRecyclerVisible();
}
)
);
}
}
When i click on button to search i got first empty response because i observe on list with got not updated yet(Its getting updated via method network call). If I press button 2nd time thats when I got need data from 1 request. How should i chain those 2 RxJava method from different class ?
1. In MVP architecture should Model layer got injected instance of abstracted presenter and connect it to model just like with view?
No. Model layer should not be directly accessing View nor Presentation layer.
Also note that it doesn't make much sense to put .observeOn(AndroidSchedulers.mainThread()) in any of your Model layer implementation.
if not how should i send data from model to presenter ?
Model should just respond to Presenters' queries. It could be a simple function call. Model does not need to hold Presenter instances to handle that.
2. ...How should i chain those 2 RxJava method from different class ?
Consider this implementation:
Model
#Override
public Observable<List<Items>> networkCallForData(String request) {
request = "volumes?q=" + request;
return api.getBook(request);
}
Presenter
// Call this once in the beginning.
private void getDataFromModel() {
compositeDisposable.add(
findActivityModel.networkCallForData(request)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
books -> {
view.setRecycler(books);
}, Throwable::printStackTrace
, () -> {
view.setRecyclerVisible();
}
)
);
}
Above implementation should be sufficient if you are getting the data only once per screen. But you mentioned something like a refresh button. For that you can make use of BehaviorSubject or BehaviorProcessor.
Presenter
private BehaviorSubject<List<Item>> items =
BehaviorSubject.create(); // You may move this line to the Model layer.
// Call this once in the beginning to setup the recycler view.
private void getDataFromModel() {
// Instead of subscribing to Model, subscribe to BehaviorSubject.
compositeDisposable.add(
items.subscribe(books -> {
// Any change in BehaviorSubject should be notified
view.setRecycler(books);
view.setRecyclerVisible();
});
}
// Trigger this on button clicks
private void refreshData() {
compositeDisposable.add(
findActivityModel.networkCallForData(request)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> {
// Refresh BehaviorSubject
items.onNext(books);
});
}
Even this might not perfectly fit your need but hope you get the idea. Also, few side notes:
Observable.fromArray() returns an observable that emits array items one at a time. Therefore, it is not very useful in this scenario.
It seems you have an object and an observable that wraps the object. If you have these two things in the same place that is usually a sign of a bad design.
private Observable> itemsObservable;
private List items;
This is not the right way to use observable but also violates single source of truth.
One possible way of refactoring this:
private BehaviorSubject<List<Items>> itemsObservable;
// private List<Items> items; // Remove this line
public Observable<List<Items>> getItemsObservable() {
return itemsObservable.hide();
}
// Call this whenever you need to update itemsObservable
private void updateItems(List<Item> newItems) {
itemsObservable.onNext(newItems);
}
So, I have just started experimenting with LiveData - I am busy with a new project, where I am using ViewModel as well as LiveData - with some of the RESTFul services I use to fetch data, they take no parameters and return some data.
A typical setup of the MVVM paradigm with LiveData looks much like this:
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
}
Now when we leave this activity, and go to a new activity, by using an Intent or some other means, and not pressing the back button (So, finalize is not called) - and then come back to MyActivity - we of course don't fetch the users again, as we should still have that data.
However, what if we did want to fetch them again?
The only way to do this properly, from what I have looked at, seems to call "setValue" on the getUsers() LiveData object
Something like this:
public class MyActivity extends AppCompatActivity {
public void onResume() {
viewModel.setActive(true);
}
}
And the ViewModel would look like this:
private final MutableLiveData<Boolean> activeLiveData = new MutableLiveData<>();
ViewModel(ViewModelRepo repo){
this.repo = repo;
results = Transformations.switchMap(activeLiveData, active ->{
if(active){
return repo.getUsers();
}else {
return AbsentLiveData.create(); //"Null live data"
}
});
}
LiveData<Users>> getUsers() {
return results;
}
//This could be called "update" with no params
void setActive(boolean active) {
activeLiveData.setValue(active);
}
The one reason I have decided to do it like this is because Google does not want us doing this:
class MyViewModel extends ViewModel {
private final PostalCodeRepository repository;
public MyViewModel(PostalCodeRepository repository) {
this.repository = repository;
}
private LiveData<String> getPostalCode(String address) {
// DON'T DO THIS
return repository.getPostCode(address);
}
}
For this reason:
If this is the implementation, the UI would need to unregister from
the previous LiveData and re-register to the new instance each time
they call getPostalCode(). Moreover, if the UI is re-created, it
triggers another call to repository.getPostCode() instead of using the
previous call’s result.
Is there a better way to get the ViewModel to "redo" its repo.getUsers() call? Perhaps I could just make a method that says "Update()" instead of "active" but still - its doing the same thing differently.
Well here you're doing the fetching in the creator of the ViewModel, which locks things in place. Usually they'd advise to fetch the data in the getter, if the data is not there already.
So a good option would be to use the regular pattern first :
private MutableLiveData<Users> users = null;
ViewModel(ViewModelRepo repo){
this.repo = repo;
}
LiveData<Users> getUsers() {
if (users = null) {
fetchUsers();
}
return users;
}
public void fetchUsers() {
users.postValue(repo.getUsers());
}
And then from your Activity/Fragment, whenever you feel necessary to "refresh the users", you'd simply call viewModel.fetchUsers();
Picture the situation in an MVP pattern where your presenter subscribes to a service returning an observer:
public void gatherData(){
service.doSomeMagic()
.observeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(new TheSubscriber());
}
Now the class TheSubscriber calls onNext a method from the view, say:
#Override public void onNext(ReturnValue value) {
view.displayWhatever(value);
}
Now, in my unit test I would like to verify that when the method gatherData() is called on a non-erroneous situation, the view's method displayWhatever(value) is called.
The question:
Is there a clean way to do this?
Background:
I'm using mockito to verify the interactions and a lot more of course
Dagger is injecting the entire presenter except for TheSubscriber
What have I tried:
Inject the subscriber and mock it in the tests. Looks a bit dirty to me, because if I want to change the way the presenter interacts with the service (Say not Rx) then I need to change a lot of tests and code.
Mock the entire service. This was not so bad, but requires me to mock a lot of methods and I didn't quite reach what I wanted.
Looked up around the internet, but no one seems to have a clean straight way of doing this
Thanks for the help
Assuming that you are using interfaces for service and view in a similar manner:
class Presenter{
Service service;
View view;
Presenter(Service service){
this.service = service;
}
void bindView(View view){
this.view = view;
}
void gatherData(){
service.doSomeMagic()
.observeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(view::displayValue);
}
}
It is possible then to provide mock to control and verify behaviour:
#Test void assert_that_displayValue_is_called(){
Service service = mock(Service.class);
View view = mock(View.class);
when(service.doSomeMagic()).thenReturn(Observable.just("myvalue"));
Presenter presenter = new Presenter(service);
presenter.bindView(view);
presenter.gatherData();
verify(view).displayValue("myvalue");
}
I know its pretty late but may it helps someone, cause i searched pretty long for a solution to your question :D
For me it worked out to add a Observable.Transformer<T, T> as followed:
void gatherData() {
service.doSomeMagic()
.compose(getSchedulerTransformer())
.subscribe(view::displayValue);
}
private <T> Observable.Transformer<T, T> getSchedulerTransformer() {
if (mTransformer == null) {
mTransformer = (Observable.Transformer<T, T>) observable -> observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
return mTransformer;
}
void setSchedulerTransformer(Observable.Transformer<Observable<?>, Observable<?>> transformer) {
mTransformer = transformer;
}
And to set the Transformer just I just passed this:
setSchedulerTransformer(observable -> {
if (observable instanceof Observable) {
Observable observable1 = (Observable) observable;
return observable1.subscribeOn(Schedulers.immediate())
.observeOn(Schedulers.immediate());
}
return null;
});
So just add a #Before method in your test and call presenter.setSchedulerTransformer and it should be able to test this :)
hope this helps and is somehow understandable :D