CompineLatest that observe multipe checked buttons Rxjava - android

I have created custom button, it has inner checked listener and I expose it as an observable with the help of a PublishSubject.
public class SwitchItem extends LinearLayout implements SwitchButton.OnCheckedChangeListener {
//other fields ...
private PublishSubject<Boolean> checkedObservable = PublishSubject.create();
//I call this method in each
private void initViews(String itemTitle, String itemStatus, boolean isChecked) {
//other initializations
if (switchButton != null) {
switchButton.setOnCheckedChangeListener(this);
}
}
#Override
public void onCheckedChanged(SwitchButton view, boolean isChecked) {
defaultItemStatusText(isChecked);
checkedObservable.onNext(isChecked);
}
//to get the observable
public Observable<Boolean> getCheckedObservable() {
return checkedObservable;
}
}
in th UI I have multiple buttons of this type, I get all of their observables and use CompineLatest to listen on any change, what I try to achieve is to enable a setting save buttton if anyone of the buttons changed its state.
Observable.combineLatest(button1, button2,button3,(cheked1, cheked2, cheked3) -> {
boolean isSettingsChanged = false;
isSettingsChanged = checked1 != inital1 || checked2 != inital2 || checked3 != inital3;
return isSettingsChanged;
}).subscribe(enable -> getView().enableSave(enable));
the problem that I have to change all of the buttons states so compineLatest start to fire its logic. I tried to use setChecked() to give them an initial values, but it doesn't work, it always wait to fire a specific button so it start to work. I don't know where the problem is.

Here's how I would approach the problem:
Observable.merge(button1.distinct(), button2.distinct(), button3.distinct())
.subscribe(ignore -> getView().enableSave(true));
By using .distinct(), you ensure the button only emits when there is a change. If you need to emit when there is a change and matches a certain value, then you'd be looking at .distinct().filter(<Predicate>).
Hope this helps

Related

Cannot read country code selected in a spinner Android

I am working on an app in which users have to select a country code, i was successful in creating a spinner for the said purpose as shown in this link:
Creating a spinner for choosing country code
But i am getting problem in reading the value selected in the spinner.
{
String abc = onCountryPickerClick();//abc is always null
}
public String onCountryPickerClick (){
ccp.setOnCountryChangeListener(new CountryCodePicker.OnCountryChangeListener() {
#Override
public void onCountrySelected() {
selected_country_code = ccp.getSelectedCountryCodeWithPlus();
}
});
return selected_country_code;
}
When String abc = onCountryPickerClick(); is being invoked, the selected_country_code value will be assigned to abc.
When your CountryCodePicker.OnCountryChangeListener's onCountrySelected() method is being invoked, the ccp.getSelectedCountryCodeWithPlus();'s value gets assigned to selected_country_code. Since String is immutable, changing selected_country_code's value won't change the value of abc, nor the return selected_country_code; will be invoked.
One of possible solutions would be to change your CountryCodePicker.OnCountryChangeListener anonymous implementation to assign the selected country value to abc e.g.
#Override
public void onCountrySelected() {
selected_country_code = ccp.getSelectedCountryCodeWithPlus();
abc = selected_country_code
}
Callbacks are not synchronous. Unfortunately, you cannot simply do String abc = onCountryPickerClick(); because what you are returning is something that is not yet set. Let's go through your code:
ccp.setOnCountryChangeListener(
new CountryCodePicker.OnCountryChangeListener() {
#Override
public void onCountrySelected() {
selected_country_code = ccp.getSelectedCountryCodeWithPlus();
}
});
The code seems to say that when the country is selected in the spinner, you assign the value of selected_country_code. Assuming this is an action triggered by the user, when you call String abc = onCountryPickerClick();, how can you be sure the user has selected anything? This is the issue. You cannot be sure that the user has already selected the option and returning the value is not enough.
You can solve this in many ways. You can for example keep propagating the callback:
public void onCountryPickerClick(OnCountryChangeListener listener){
ccp.setOnCountryChangeListener(listener);
}
// Anywhere you call this
onCountryPickerClick(new CountryCodePicker.OnCountryChangeListener() {
#Override
public void onCountrySelected() {
// Here do whatever you want with the selected country
}
});
The above approach is not very different than what you have now. There are other options. You could use java observables i.e.:
class CountryCodeObservable extends Observable {
private String value;
public CountryCodeObservable(String value) {
this.value = value;
}
public void setCountryCode(String countryCode) {
value = countryCode;
setChanged();
notifyObservers(value);
}
}
public CountryCodeObservable onCountryPickerClick(){
CountryCodeObservable retValue = new CountryCodeObservable("");
ccp.setOnCountryChangeListener(
new CountryCodePicker.OnCountryChangeListener() {
#Override
public void onCountrySelected() {
retValue.setCountryCode(ccp.getSelectedCountryCodeWithPlus());
}
});
return retValue;
}
// Then when calling this method you can do something like:
CountryCodeObservable observable = onCountryPickerClick();
observable.addObserver((obj, arg) -> {
// arg is the value that changed. You'll probably need to cast it to
// a string
});
The above example lets you add more than one observable. It might be too much for your use case, I just thought it illustrates another approach and also the asynchronicity of this situation.
Again, there are even more ways to solve this, the key is that you can't simply return a string and hope it changes when the user selects anything.

Searching a LiveData of PagedList in RecyclerView by Observing ViewModel

With android Paging library it is really easy to load data from Database in chunks and ViewModel provides automatic UI update and data survival. All these frameworks modules help us create a great app in android platform.
A typical android app has to show a list of items and allows user to search that list. And this what I want to achieve with my app. So I have done an implementation by reading many documentations, tutorials and even stackoverflow answers. But I am not so sure whether I am doing it correctly or how I supposed to do it. So below, I have shown my way of implementing paging library with ViewModel and RecyclerView.
Please, review my implementation and correct me where I am wrong or show me how I supposed to do it. I think there are many new android developers like me are still confused how to do it correctly as there is no single source to have answers to all your questions on such implementation.
I am only showing what I think is important to show. I am using Room. Here is my Entity that I am working with.
#Entity(tableName = "event")
public class Event {
#PrimaryKey(autoGenerate = true)
public int id;
public String title;
}
Here is DAO for Event entity.
#Dao
public interface EventDao {
#Query("SELECT * FROM event WHERE event.title LIKE :searchTerm")
DataSource.Factory<Integer, Event> getFilteredEvent(String searchTerm);
}
Here is ViewModel extends AndroidViewModel which allows reading and searching by providing LiveData< PagedList< Event>> of either all events or filtered event according to search text. I am really struggling with the idea that every time when there is a change in filterEvent, I'm creating new LiveData which can be redundant or bad.
private MutableLiveData<Event> filterEvent = new MutableLiveData<>();
private LiveData<PagedList<Event>> data;
private MeDB meDB;
public EventViewModel(Application application) {
super(application);
meDB = MeDB.getInstance(application);
data = Transformations.switchMap(filterEvent, new Function<Event, LiveData<PagedList<Event>>>() {
#Override
public LiveData<PagedList<Event>> apply(Event event) {
if (event == null) {
// get all the events
return new LivePagedListBuilder<>(meDB.getEventDao().getAllEvent(), 5).build();
} else {
// get events that match the title
return new LivePagedListBuilder<>(meDB.getEventDao()
.getFilteredEvent("%" + event.title + "%"), 5).build();
}
}
});
}
public LiveData<PagedList<Event>> getEvent(Event event) {
filterEvent.setValue(event);
return data;
}
For searching event, I am using SearchView. In onQueryTextChange, I wrote the following code to search or to show all the events when no search terms is supplied meaning searching is done or canceled.
Event dumpEvent;
#Override
public boolean onQueryTextChange(String newText) {
if (newText.equals("") || newText.length() == 0) {
// show all the events
viewModel.getEvent(null).observe(this, events -> adapter.submitList(events));
}
// don't create more than one object of event; reuse it every time this methods gets called
if (dumpEvent == null) {
dumpEvent = new Event(newText, "", -1, -1);
}
dumpEvent.title = newText;
// get event that match search terms
viewModel.getEvent(dumpEvent).observe(this, events -> adapter.submitList(events));
return true;
}
Thanks to George Machibya for his great answer. But I prefer to do some modifications on it as bellow:
There is a trade off between keeping none filtered data in memory to make it faster or load them every time to optimize memory. I prefer to keep them in memory, so I changed part of code as bellow:
listAllFood = Transformations.switchMap(filterFoodName), input -> {
if (input == null || input.equals("") || input.equals("%%")) {
//check if the current value is empty load all data else search
synchronized (this) {
//check data is loaded before or not
if (listAllFoodsInDb == null)
listAllFoodsInDb = new LivePagedListBuilder<>(
foodDao.loadAllFood(), config)
.build();
}
return listAllFoodsInDb;
} else {
return new LivePagedListBuilder<>(
foodDao.loadAllFoodFromSearch("%" + input + "%"), config)
.build();
}
});
Having a debouncer helps to reduce number of queries to database and improves performance. So I developed DebouncedLiveData class as bellow and make a debounced livedata from filterFoodName.
public class DebouncedLiveData<T> extends MediatorLiveData<T> {
private LiveData<T> mSource;
private int mDuration;
private Runnable debounceRunnable = new Runnable() {
#Override
public void run() {
DebouncedLiveData.this.postValue(mSource.getValue());
}
};
private Handler handler = new Handler();
public DebouncedLiveData(LiveData<T> source, int duration) {
this.mSource = source;
this.mDuration = duration;
this.addSource(mSource, new Observer<T>() {
#Override
public void onChanged(T t) {
handler.removeCallbacks(debounceRunnable);
handler.postDelayed(debounceRunnable, mDuration);
}
});
}
}
And then used it like bellow:
listAllFood = Transformations.switchMap(new DebouncedLiveData<>(filterFoodName, 400), input -> {
...
});
I usually prefer to use DataBiding in android. By using two way Data Binding you don't need to use TextWatcher any more and you can bind your TextView to the viewModel directly.
BTW, I modified George Machibya solution and pushed it in my Github. For more details you can see it here.
I will strong advice to start using RxJava and you it can simplify the entire problem of looking on the search logic.
I recommend in the Dao Room Class you implement two method, one to query all the data when the search is empty and the other one is to query for the searched item as follows. Datasource is used to load data in the pagelist
#Query("SELECT * FROM food order by food_name")
DataSource.Factory<Integer, Food> loadAllFood();
#Query("SELECT * FROM food where food_name LIKE :name order by food_name")
DataSource.Factory<Integer, Food> loadAllFoodFromSearch(String name);
In the ViewModel Class we need to two parameter that one will be used to observed searched text and that we use MutableLiveData that will notify the Views during OnChange. And then LiveData to observe the list of Items and update the UI.
SwitchMap apply the function that accept the input LiveData and generate the corresponding LiveData output. Please find the below Code
public LiveData<PagedList<Food>> listAllFood;
public MutableLiveData<String> filterFoodName = new MutableLiveData<>();
public void initialFood(final FoodDao foodDao) {
this.foodDao = foodDao;
PagedList.Config config = (new PagedList.Config.Builder())
.setPageSize(10)
.build();
listAllFood = Transformations.switchMap(filterFoodName, outputLive -> {
if (outputLive == null || outputLive.equals("") || input.equals("%%")) {
//check if the current value is empty load all data else search
return new LivePagedListBuilder<>(
foodDao.loadAllFood(), config)
.build();
} else {
return new LivePagedListBuilder<>(
foodDao.loadAllFoodFromSearch(input),config)
.build();
}
});
}
The viewModel will then propagate the LiveData to the Views and observe the data onchange. In the MainActivity then we call the method initialFood that will utilize our SwitchMap function.
viewModel = ViewModelProviders.of(this).get(FoodViewModel.class);
viewModel.initialFood(FoodDatabase.getINSTANCE(this).foodDao());
viewModel.listAllFood.observe(this, foodlistPaging -> {
try {
Log.d(LOG_TAG, "list of all page number " + foodlistPaging.size());
foodsactivity = foodlistPaging;
adapter.submitList(foodlistPaging);
} catch (Exception e) {
}
});
recyclerView.setAdapter(adapter);
For the first onCreate initiate filterFoodName as Null so that to retrieve all items.
viewModel.filterFoodName.setValue("");
Then apply TextChangeListener to the EditText and call the MutableLiveData that will observe the Change and update the UI with the searched Item.
searchFood.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i,
int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int
i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
//just set the current value to search.
viewModel.filterFoodName.
setValue("%" + editable.toString() + "%");
}
});
}
Below is my github repo of full code.
https://github.com/muchbeer/PagingSearchFood
Hope that help

manage null boolean with RxJava2 PublishSubject

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

RxJava and MVP in Android app

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

Android: How to validate a set of fields without using TextWatcher and TextChangeListener

I have a number of EditText in an Activity. On clicking the submit button, I want to validate them, and prevent submission if there are errors in those EditText objects. I don't want to use TextWatcher because I don't want the methods to get fired at every single change. It does not make sense for an overall validation before submission. Is there a method that lets us loop through an array of the controls of the form? Thanks.
You have two options:
1) Create a Utils class with static methods for ensuring that the fields are valid.
i.e. toy example for checking email
public class Utils{
public static boolean isValidEmail(String str){
return str.contains("#");
}
}
and do so for checking the various fields (phone #, email, name, etc...). In your Activity that has the EditText(s), when you try to submit them, have something like:
public boolean validateFields(){
boolean result = true;
if(!Utils.isValidEmail(mEmailEdit.getText().toString()){
mEmailEdit.setError("Invalid email!");
result = false;
}
if(!Utils.isValidName(mEmailEdit.getText().toString()){
mNameEdit.setError("Invalid name!");
result = false;
}
return result;
}
This is a very simple idea of what you would do. Call validateFields() when clicking the submit button, and if the validateFields() method returns false, then do not proceed with the fields. If it returns true, well then all fields are valid and call another method to submit the data to w/e you are using it for.
2) (Best option for larger projects) Create an interface, call it Validator with a boolean-return function called validate(). This validator interface is extended for each various validation you wish to do, and you create a new interface like so:
public interface Validator{
public boolean validate(String s);
}
public interface EmailValidator extends Validator{
#Override
public boolean validate(String s){
return s.contains("#");
}
}
And extend a new EditText class view that has a Validator interface field, with a getter/setter. Then, in the validateFields() method, we do the same thing except call each EditText's validation interface's validate() method. There are a few more subtleties for this and I can type this all out if interested on how to do exactly. Let me know if that helps
The most straight forward way to do this is to get references to each of your sub views after you create the main view via setContentView(..). Use findViewById() to get references to each of them.
Then in your submit button click handler grab the inputs from each of them via something like nameField.getText() and do whatever validation you want. And if it fails show the error to the user in some fashion.
So, taking ideas from Lucas, creating custom components such as a DateEditText extending EditText and implementing a Validator interface, in my activity button for update onclick, I call this method that I wrote "isValidViewGroup(ViewGroup viewGroup), it will recursively go through all views, starting with the given viewGroup, and check children views, until it meets one implementing Validator and then call its isValid() method. It stops as soon as it finds an invalid one, or go through the end of views. Here's the method:
...
private boolean isValidViewGroup(ViewGroup viewGroup) {
int viewGroupChildCount = viewGroup.getChildCount();
View view = null;
for (int i = 0; i < viewGroupChildCount;i++) {
view = viewGroup.getChildAt(i);
if (view instanceof ViewGroup){
if (!isValidViewGroup((ViewGroup) view)){
return false;
}
} else {
if (view instanceof Validator){
if (!((Validator)view).isValid()){
return false;
}
}
}
}
return true;
}
...
I'm sure there could be a better, more efficient way, but for the moment that works really fine.

Categories

Resources