I am trying to implement a screen in Android app using MVP architecture and using RxJava and RxBinding on the View side.
Basically I have 2 Spinners, 1 TextEdit and a button that's disabled by default. I want to enable the button when Spinners have items selected and text field is not empty. Here is the code:
Observable.combineLatest(
RxAdapterView.itemSelections(mFirstSpinner),
RxAdapterView.itemSelections(mSecondSpinner),
RxTextView.textChanges(mEditText),
new Func3<Integer, Integer, CharSequence, Boolean>() {
#Override
public Boolean call(Integer first, Integer second, CharSequence value) {
return !TextUtils.isEmpty(value);
}
}).subscribe(new Action1<Boolean>() {
#Override
public void call(Boolean enable) {
mButton.setEnabled(enable);
}
});
The question now is how to integrate that into MVP pattern. Ideally the "business logic" of enabling the button should be in the presenter. What's the best way to achieve this? I am thinking of passing the original observers into the presenter somehow (side question is how?), and the presenter would combine those observers and it would have the logic of enabling the button. In the end, it would just call View to modify button state.
Are there any better options? Are there any good examples of MVP with RxJava on View side?
My proposition:
You are on the right track. However RxBinding logic should still be in the view. I would move the logic connected with deciding whether to enable button or not into presenter.
Define a model holding value from all fields you would like to check:
private class ViewValuesModel {
public Integer adapter1Value;
public Integer adapter2Value;
public CharSequence textValue;
public ViewValuesModel(Integer adapter1Value, Integer adapter2Value, CharSequence textValue) {
this.adapter1Value = adapter1Value;
this.adapter2Value = adapter2Value;
this.textValue = textValue;
}
}
The inside a view create an Observable:
Observable observable = Observable.combineLatest(
RxAdapterView.itemSelections(mFirstSpinner),
RxAdapterView.itemSelections(mSecondSpinner),
RxTextView.textChanges(mEditText),
new Func3<Integer, Integer, CharSequence, ViewValuesModel>() {
#Override
public ViewValuesModel call(Integer first, Integer second, CharSequence value) {
return new ViewValuesModel(first, second, value);
}
}
)
Then pass this Observable to presenter:
mPresenter.observeChoosableFieldChanges(observable).
Inside presenter do the rest:
observable
.map(new Func1<ViewValuesModel, Boolean>() {
#Override
public Booleancall(ViewValuesModel viewStates) {
return !TextUtils.isEmpty(viewStates.textValue);
}
})
.subscribe(new Action1<Boolean>() {
#Override
public void call(Boolean enable) {
if (enable) {
view.enableButton();
}
}
});
you can enumerate your sources and pass Pair value events to a Subject/Processor where you can do the logic whether or not to enable the button and post boolean events. The guy which updates the button from the presenter will subscribe to this Subject/Processor.
Like this you have the flexibility to change sources and logic without changing the Presenter-View contract.
Essentially you can have 2 absolutely decoupled components in the Presenter:
1) component which listens to the incoming view events and produces a stream of action to enable or disable the button
2) a component which listens to enable/disable actions and updates the view respectively (this you can also achieve with Google's Binding Library)
This way you can enable multiple decoupled chains of interaction and yet easy to maintain due components triviality and clarity of the flow connections.
You can also use smth like RHub library. You can find components example here
Related
I have a two queries which return two long values. I am setting these two long values to be displayed in individual text views. Finally I have a third text view which displays the combined value of both longs. I am having a problem getting the combined total to show as its setting the value before the livedata is returned.
Below is a snippet of the code
private void getData() {
mViewModelReframing.totalWorkouts(pkUserId).observe(getViewLifecycleOwner(), new Observer<List<ModelStatsTotalWorkouts>>() {
#Override
public void onChanged(List<ModelStatsTotalWorkouts> modelStatsTotalWorkouts) {
for (ModelStatsTotalWorkouts list : modelStatsTotalWorkouts) {
totalReframeWorkouts = list.getTotalWorkouts();
}
if (totalReframeWorkouts == 0) {
tvTotalReframes.setText(0 + getString(R.string.workouts_empty));
} else {
tvTotalReframes.setText("" + totalReframeWorkouts);
}
}
});
mViewModelCheckIn.totalWorkouts(pkUserId).observe(getViewLifecycleOwner(), new Observer<List<ModelStatsTotalWorkouts>>() {
#Override
public void onChanged(List<ModelStatsTotalWorkouts> tableCheckIns) {
for (ModelStatsTotalWorkouts list : tableCheckIns) {
totalCheckInWorkouts = list.getTotalWorkouts();
}
tvTotalCheckIns.setText("" + totalCheckInWorkouts);
// Combine both longs together for a combined total.
totalWorkouts = totalReframeWorkouts + totalCheckInWorkouts;
tvTotalWorkouts.setText("" + totalWorkouts);
}
});
}
Is there a better way to write the logic to achieve the desired result without the issue of the livedata not being returned fast enough?
Whenever you use independent Reactive streams like this (LiveData, RxJava, etc) you are going to have race conditions. You need to make explicit the dependencies for an action to happen - in this case your ability to update the UI in the way that you want had dependencies on BOTH APIs returning. This is the RxJava equivalent of zip. A few tips:
Consider using only a single Viewmodel for a view. The viewmodel should really be preparing data for your view specificially. In this case, it should really be that singular ViewModel that handles combining this data before passing it to your vew at all.
Barring that, since you've chosen LiveData here, you can do what you want by using a MediatorLiveData. Essentially, it acts as a composite stream source that depends on whichever other LiveData streams you add to it as described by that article. In this way, you can explicitly wait for all the needed values to arrive before you try to update the UI.
I solved the question by using this method:
public LiveData<List<ModelStatsTotalWorkouts>> totalWorkoutsCombined(long userId) {
LiveData liveData1 = database.getUsersDao().totalReframeWorkouts(userId);
LiveData liveData2 = database.getUsersDao().totalCheckInWorkouts(userId);
MediatorLiveData liveDataMerger = new MediatorLiveData<>();
liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
return liveDataMerger;
}
In an Android app using Architecture Components I have the following view model:
public class MainViewModel extends AndroidViewModel {
private final MutableLiveData<List<String>> mUnchecked = new MutableLiveData<>();
private LiveData<List<String>> mChecked;
public void setUnchecked(List<String> list) {
mUnchecked.setValue(list);
}
public LiveData<List<String>> getChecked() { // OBSERVED BY A FRAGMENT
return mChecked;
}
public MainViewModel(Application app) {
super(app);
mChecked = Transformations.switchMap(mUnchecked,
list-> myDao().checkWords(list));
}
The purpose of the above switchMap is to check, which of the words passed as a list of strings, do exist in a Room table:
#Dao
public interface MyDao {
#Query("SELECT word FROM dictionary WHERE word IN (:words)")
LiveData<List<String>> checkWords(List<String> words);
The above code works well for me!
However I am stuck with wanting something slightly different -
Instead of the list of strings, I would prefer to pass a map of strings (words) -> integers (scores):
public void setUnchecked(Map<String,Integer> map) {
mUnchecked.setValue(map);
}
The integers would be word scores in my game. And once the checkWords() has returned the results, I would like to set the scores to null for the words not found in the Room table and leave the other scores as they are.
The programming code would be easy (iterate through mChecked.getValue() and set to null for the words not found in the list returned by the DAO method) - but how to "marry" it with my LiveData members?
TL;DR
I would like to change my view model to hold maps instead of the lists:
public class MainViewModel extends AndroidViewModel {
private final MutableLiveData<Map<String,Integer>> mUnchecked = new MutableLiveData<>();
private final MutableLiveData<Map<String,Integer>> mChecked = new MutableLiveData<>();
public void setUnchecked(Map<String,Integer> map) {
mUnchecked.setValue(map);
}
public LiveData<Map<String,Integer>> getChecked() { // OBSERVED BY A FRAGMENT
return mChecked;
}
public MainViewModel(Application app) {
super(app);
// HOW TO OBSERVE mUnchecked
// AND RUN myDao().checkWords(new ArrayList<>(mUnchecked.getValue().keys()))
// WRAPPED IN Executors.newSingleThreadScheduledExecutor().execute( ... )
// AND THEN CALL mChecked.postValue() ?
}
How to achieve that please? Should I extend MutableLiveData or maybe use MediatorLiveData or maybe use Transformations.switchMap()?
UPDATE:
I will try the following tomorrow (today is too late in the evening) -
The Dao method I will change to return a list instead of LiveData:
#Query("SELECT word FROM dictionary WHERE word IN (:words)")
List<String> checkWords(List<String> words);
And then I will try to extend the MutableLiveData:
private final MutableLiveData<Map<String,Integer>> mChecked = new MutableLiveData<>();
private final MutableLiveData<Map<String,Integer>> mUnchecked = new MutableLiveData<Map<String,Integer>>() {
#Override
public void setValue(Map<String,Integer> uncheckedMap) {
super.setValue(uncheckedMap);
Executors.newSingleThreadScheduledExecutor().execute(() -> {
List<String> uncheckedList = new ArrayList<>(uncheckedMap.keySet());
List<String> checkedList = WordsDatabase.getInstance(mApp).wordsDao().checkWords(uncheckedList);
Map<String,Integer> checkedMap = new HashMap<>();
for (String word: uncheckedList) {
Integer score = (checkedList.contains(word) ? uncheckedMap.get(word) : null);
checkedMap.put(word, score);
}
mChecked.postValue(checkedMap);
});
}
};
Well, what you have there in the update probably works, though I wouldn't create a new Executor for every setValue() call — create just one and hold onto it in your MutableLiveData subclass. Also, depending on your minSdkVersion, you might use some of the Java 8 stuff on HashMap (e.g., replaceAll()) to simplify the code a bit.
You could use MediatorLiveData, though in the end I think it would result in more code, not less. So, while from a purity standpoint MediatorLiveData is a better answer, that may not be a good reason for you to use it.
Frankly, this sort of thing isn't what LiveData is really set up for, IMHO. If this were my code that I were working on right now, I'd be using RxJava for the bulk of it, converting to LiveData in the end. And, I'd have as much of this as possible in a repository, rather than in a viewmodel. While your unchecked-to-checked stuff would be a tricky RxJava chain to work out, I'd still prefer it to the MutableLiveData subclass.
What EpicPandaForce suggests is an ideal sort of LiveData-only approach, though I don't think he is implementing your algorithm quite correctly, and I am skeptical that it can be adapted easily to your desired algorithm.
In the end, though, the decision kinda comes down to: who is going to see this code?
If this code is for your eyes only, or will live in a dusty GitHub repo that few are likely to look at, then if you feel that you can maintain the MutableLiveData subclass, we can't really complain.
If this code is going to be reviewed by co-workers, ask your co-workers what they think.
If this code is going to be reviewed by prospective employers... consider RxJava. Yes, it has a learning curve, but for the purposes of getting interest from employers, they will be more impressed by you knowing how to use RxJava than by you knowing how to hack LiveData to get what you want.
Tricky question!
If we check the source code for Transformations.switchMap, we see that:
1.) it wraps the provided live data with a MediatorLiveData
2.) if the wrapped live data emits an event, then it invokes a function that receives the new value of wrapped live data, and returns a "new" live data of a different type
3.) if the "new" live data of a different type differs from the previous one, then the observer of the previous one is removed, and it's added to the new one instead (so that you only observe the newest LiveData and don't accidentally end up observing an old one)
With that in mind, I think we can chain your switchMap calls and create a new LiveData whenever myDao().checkWords(words) changes.
LiveData<List<String>> foundInDb = Transformations.switchMap(mWords, words -> myDao().checkWords(words));
LiveData<Map<String, Integer>> found = Transformations.switchMap(foundInDb, (words) -> {
MutableLiveData<Map<String, Integer>> scoreMap = new MutableLiveData<>();
// calculate the score map from `words` list
scoreMap.setValue(map);
return scoreMap;
});
this.mFound = found;
Please verify if what I'm telling you is correct, though.
Also if there are a bunch of words, consider using some async mechanism and scoreMap.postValue(map).
I'm developing application wherein I want to use MVVM pattern. Currently, all events from xml are handled by the activity which pass them next to ViewModel. E.g. user clicks login button, the event is handled by activity; now the activity call view model's method, inside this method I'm calling RxFirebase (Rx wrapper on Firebase) method which returns Observable, subscribe to them and return it; in view I'm again subscribe to this observable for doing UI update. This situation is presented below.
My question is if this approach is correct? In my opinion, the better solution is to handle the error in ViewModel, but how then I can update UI? One of the solutions is to create interface, e.g. ShowMessageListener, next pass it to ViewModel and use to show message, but I prefer harness RxJava to this.
View method:
public void onLoginClick(View view) {
mBinding.clProgress.setVisibility(View.VISIBLE);
mViewModel.onLoginClick().subscribe(authResult -> {
mBinding.clProgress.setVisibility(View.GONE);
startAnotherActivity();
}, throwable -> {
mBinding.clProgress.setVisibility(View.GONE);
if (throwable instanceof FirebaseApiNotAvailableException) {
Snackbar.make(mBinding.getRoot(), R.string.google_play_services_unavilable, Snackbar.LENGTH_LONG).show();
} else {
Snackbar.make(mBinding.getRoot(), throwable.getMessage(), Snackbar.LENGTH_LONG).show();
}
});
}
ViewModel method:
public Observable<AuthResult> onLoginClick() {
Observable<AuthResult> observable = RxFirebaseAuth.signInWithEmailAndPassword(mAuth, mEmail.get(), mPassword.get());
observable.subscribe(authResult -> {
//save user
}, throwable -> {
//handle error
});
return observable;
}
Your answer is almost correct except that you should really seperate View and (Business)-Logic. This would be the attempt if you use databinding which is highly recommend when using Architecture Components.
That means that everything which updates the UI should be in your View, everything which is not relevant for the view should be in the ViewModel.
That means that you can pass your ViewModel to your Layout, which has a onClick and call the Method in the ViewModel. Example:
<?xml version="1.0" encoding="utf-8"?>
<layout ..>
<data><variable name="viewModel" type="YourVm" /></data>
<Button onClick="#{viewModel::onButtonClick}
</layout>
Now you can handle the onClick inside your ViewModel like
public void onClick(View view) {
Log.d("Click", "My Button was clicked");
}
If you "really" want to observe for errors from your View you could either Create an ObservableBoolean which is set to True onec there's an error and subscribe for changes. You can put it inside the ViewModel like:
public final ObservableBoolean observableError = new ObservableBoolean();
public void onClick(...) { observableError.set(true); }
Now you can observe the Boolean inside your View
yourViewModel.obserableError.observe(this, result -> {
// do your error stuff
});
If you don't use Databinding it's almost the same except that you pass a ClickListener to the Button.
Means that you listen for the OnClick in your View, call the "processing"-method in your ViewModel and update the ObservableBoolean if an error occured. Since your a Listening for changes you can process the SnackBar stuff inside your View.
Snackbar and everything which involves the view should really be seperated from the ViewModel except a navigator. In this case you should create WeakReferences to avoid leaks.
Take care that the ObservableBoolean is NOT part of RxJava. It's part of Architecture Components.
If you want to solve it using RxJava you could create a PublishSubject in your ViewModel like:
Viewmodel.java
public final PublishSubject<String> ps = PublishSubject.create<>()
public void onClick(...) { ps.next("my evil error string"); }
And finally Observe it in your view
myViewModel.ps.subscribe( data -> {...}, error -> { ... } )
Take care that you dispose your RxJava Subscriptions in onCleared() which is in your ViewModel interface.
Edit: I haven't tested the code since i have only Kotlin Projects at the moment but should work in java.
Found an issue in your code that you didnt validate if mBinding is null. This may be null since you subscribe for changes and try to create the SnackBar in the View which may be disposed already. Always use if (mBinding != null) Snackbar.snackysnacky(..)
I'm implementing the MVP design pattern. My presenter receives the new values from the view. I want to manage the state of a next button by automatically check if everything is valid when values are updated on the view.
In my form I have an optional part which is displayed only if the user select the correct option.
In this optional part I have a binary question. If the part is not displayed I need to set the value of the question to null on the Presenter side.
For example, the user select the option and the optional part is displayed. The user select the answer. Then the user change the option and the optional part is hidden. In that case I need to set the answer to the optional question to null, for the answer to not be already selected if the user display the optional part again.
To do so, I call a method on the Presenter with a null value instead of true/false.
Here is the code:
private final PublishSubject<Boolean> mObsOptionalAnswer = PublishSubject.create();
public MyPresenter(){
// Combine all the values together to enable/disable the next button
Observable.combineLatest(
// ... other fields
// I need this to return false if the optional part is
// displayed but nothing is selected
mObsOptionalAnswer.map(this::isValid),
(...) -> ...
).subscrible(enable ->{
mView.enableBtn(enable);
});
}
public void myFunction(Boolean isSomething){
// ... some code
mObsOptionalAnswer.onNext(isSomething);
}
private boolean isValid(Boolean value){
return value != null;
}
The problem is, since RxJava 2, null values are not allowed in the onNext() method.
So, how am I supposed to manage that?
If you want to be able to send a null value, you can use a wrapper. In this configuration, you send the wrapper, which isn't null even if the value itself is.
public class BooleanWrapper {
public final Boolean value;
public BooleanWrapper(Boolean value) {
this.value = value;
}
}
Your PublishSubject<Boolean> becomes a PublishSubject<BooleanWrapper> and you just have to create the wrapper and de-reference your Boolean when needed :
mObsOptionalAnswer.onNext(new BooleanWrapper(isSomething));
and
mObsOptionalAnswer.map(wrapper -> this.isValid(wrapper.value))
If you need to do that more than once in your code, you can create a generic wrapper (as described by this tutorial) :
public class Optional<M> {
private final M optional;
public Optional(#Nullable M optional) {
this.optional = optional;
}
public boolean isEmpty() {
return this.optional == null;
}
public M get() {
return optional;
}
}
you could use a constante Boolean object
public static final Boolean RESET_VALUE = new Boolean(false);
and you can emit this instead of emitting null. The receiver would have to check against this instance and behaving accordingly. Eg.
.subscrible(enable ->{
if (enable != RESET_VALUE) {
mView.enableBtn(enable);
}
});
New to RxJava/RxAndroid and am finding the lack of examples disturbing. As a way to jump into using Rx, id like to try to get something small working. Basically, if an EditText has text entered into it, then enable a Button below it.
I came across this answer, but the authors edit doesnt really show how to completely implement something like this.
From what I've gathered, I can use RxBindings to create an Observable like:
Observable<CharSequence> observable = RxTextView.textChanges(mEditText);
Supposedly I would now need to .subcribe() an Observer to watch for changes in observable, but am unsure how this would be done.
Also, how would you create the EditTexts Observable without using RxBindings if needed?
Edit: Although Retrolambda exists, answers showing how to implement this without lambdas (or both) would be helpful.
Observable<CharSequence> observable = RxTextView.textChanges(mEditText);
observable.map(new Func1<CharSequence, Boolean>() {
#Override
public Boolean call(CharSequence charSequence) {
return charSequence.length() > 0;
}
}).subscribe(new Subscriber<Boolean>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Boolean aBoolean) {
mButton.setEnabled(aBoolean);
}
});
Don't forget to keep a reference to the subscription and unsubscribe when you no longer need it (eg. in onDestroy).
RxJava-Android-Samples contains RxJava examples for Android. Check it out. You might wanna check out the Form Validation example.
Also, how would you create the EditTexts Observable without using
RxBindings if needed?
You can check out the implementation. It's open source. Internally it uses a TextWatcher to monitor the changes and emits items when the text changes.
In order to subscribe to Observable<CharSequence>, you would do something like this.
Observable<CharSequence> observable = RxTextView.textChanges(mEditText).skip(1);
mButton.setEnabled(false)
observable.subscribe(mButton -> mButton.setEnabled(true));
If you're not using retrolambda, you could do something like:
Observable<CharSequence> observable = RxTextView.textChanges(mEditText).skip(1);
mButton.setEnabled(false);
observable.subscribe(new Action1<CharSequence>(){
#Override
public void call(CharSequence c) {
mButton.setEnabled(true);
}
});
As for the second part of your question: to be honest, I'm not sure but I would guess that you would add a TextWatcher on the EditText and fire an event each time the text changes (using Observable.just(charSequenceAfterEdit)).