Is it possible to bind one ObservableField to another?

I understand that the purpose of Android library binding is to view data and automatically update when this data changes.

Question: Is it possible that data can observe other data? For example, can I set the ObservableField to "or" bind to the value of another or a set of other ObservableField s? Currently, I have done this manually - every time any of the โ€œdependentโ€ ObservableField changes, I compute the dependent field and update its value.

More details

My use case is that I want all the "logic" to be outside the view - so I want to put all my logic in the "data" class ( ViewModel , if possible). I have a button whose state I want to set to enable / disable depending on the contents of several other fields. This example illustrates what I mean.

My layout file is as follows

 <layout> <data> <variable name="register" class="com.example.RegisterViewModel"/> </data> <LinearLayout> <EditText android:id="@+id/edUsername" android:text="@{register.username}"/> <EditText android:id="@+id/edPassword" /> <EditText android:id="@+id/edConfirm" /> <Button android:text="Register" android:enabled="@{register.isValid}" /> </LinearLayout> </layout> 

And my view code is as follows:

 class RegisterView extends View { @Override protected void onFinishInflate() { RegisterViewBinding binding = DataBindingUtil.bind(this); RegisterViewModel register = new RegisterViewModel(); binding.setRegister(register); binding.edPassword.setOnFocusChangeListener(new OnFocusChangeListener(){ @Override public void onFocusChange(View v, boolean hasFocus){ register.updateUsername(edPassword.getText().toString()); } }); //Similarly for other fields. } } 

Here is my ViewModel

 class RegisterViewModel { public final ObservableField<String> username = new ObservableField<>(); private final ObservableField<String> password = new ObservableField<>(); private final ObservableField<String> confirmPassword = new ObservableField<>(); public final ObservableBoolean isValid; //Dependee Observables - isValid depends on all of these private final ObservableBoolean isUsernameValid = new ObservableBoolean(); private final ObservableBoolean isPasswordValid = new ObservableBoolean(); private final ObservableBoolean arePasswordsSame = new ObservableBoolean(); public RegisterViewModel(){ //Can this binding be made observable so that isValid automatically //updates whenever isUsernameValid/isPasswordValid/arePasswordsSame change? isValid = new ObservableBoolean(isUsernameValid.get() && isPasswordValid.get() && arePasswordsSame.get()); } public void updateUsername(String name) { username.set(name); isUsernameValid.set(ValidationUtils.validateUsername(name)); updateDependents(); } public void updatePassword(String pwd) { password.set(pwd); isPasswordValid.set(ValidationUtils.validatePassword(pwd)); updateDependents(); } public void updateConfirmPassword(String cnf) { confirmPassword.set(cnf); arePasswordsSame.set(password.get().equalsIgnoreCase(cnf.get())); updateDependents(); } //Looking to avoid this altogether private void updateDependents() { isValid.set(isUsernameValid.get() && isPasswordValid.get() && arePasswordsSame.get()); } } 
+8
android data-binding
source share
3 answers

It is not possible to bind data to two ObservableField using the bind syntax in Android data bind. However, you can associate them with the code:

 class RegisterViewModel { public final ObservableField<String> username = new ObservableField<>(); public final ObservableField<String> password = new ObservableField<>(); public final ObservableField<String> confirmPassword = new ObservableField<>(); public final ObservableBoolean isValid = new ObservableBoolean(); private boolean isUsernameValid; private boolean isPasswordValid; private boolean arePasswordsSame; public RegisterViewModel() { // You can use 3 different callbacks, but I'll use just one here // with 'if' statements -- it will save allocating 2 Object. OnPropertyChangedCallback callback = new OnPropertyChangedCallback() { @Override public void onPropertyChanged(Observable sender, int propertyId) { if (sender == username) { isUsernameValid = ValidationUtils.validateUsername(name); } else if (sender == password) { isPasswordValid = ValidationUtils.validatePassword(pwd); } else if (sender == confirmPassword) { arePasswordsSame = password.get() .equalsIgnoreCase(confirmPassword.get()); } else { // shouldn't get here... } isValid.set(isUsernameValid && isPasswordValid && arePasswordsSame); } }; username.addOnPropertyChangedCallback(callback); password.addOnPropertyChangedCallback(callback); confirmPassword.addOnPropertyChangedCallback(callback); } } 

Here I suggested that the empty username, password and confirmPassword are not valid. Seems safe.

I do not see the huge need for private ObservableField s. ObservableField was designed for binding to the user interface, and if you cannot, you can use other data types. If you find them useful for internal snapping using callbacks like the ones above, go to it.

+14
source share

I would suggest reconsidering your approach. One of the main benefits of data binding is the use of more expressive presentation code (in this case, XML). While there is a balance between how much work you really want to do in XML and in the presentation model, your case is a great example of too much work being done in the presentation model. In your code, these are not observable fields that depend on other fields, but view data that depends on data from other views. An observable field is just a representation of this data, and when possible, you should look for creating dependencies in the presentation layer, not in the data layer.

The approach I would like to propose is to start at the presentation level (XML) and assume that you do not have a holistic view model, but only data attached to the views. E.g. you can start with something like this:

 <layout> <LinearLayout> <EditText android:text="@{username}"/> <EditText text="@{password}" /> <EditText text="@{confirmPassword}" /> <Button android:text="Register" android:enabled="@{password.equals(confirmPassword) && ...password validation...}" /> </LinearLayout> </layout> 

After this first step, you will quickly realize that the password verification logic does not make sense here, since it is not trivial, so you should go:

 <layout> <import "com.example.ValidationUtils"/> <LinearLayout> <EditText android:text="@{username}"/> <EditText text="@{password}" /> <EditText text="@{confirmPassword}" /> <Button android:text="Register" android:enabled="@{password.equals(confirmPassword) && ValidationUtils.validatePassword(password)}" /> </LinearLayout> 

At this point, you just need a container for the username, password, and Password fields so you can pass them in, so you just add the viewModel variable.

 <layout> <import "com.example.ValidationUtils"/> <variable name="viewModel" type="com.example.Register"/> <LinearLayout> <EditText android:text="@{viewModel.username}"/> <EditText text="@{viewModel.password}" /> <EditText text="@{viewModel.confirmPassword}" /> <Button android:text="Register" android:enabled="@{password.equals(confirmPassword) && ValidationUtils.validatePassword(password)}" /> </LinearLayout> 

Notice how much nicer this is, and you didn't even need to see Java code.

PS: you could also replace the included expression with something like ValidationUtils.validateEntries(username, password, confirmPassword) if you want. This is a more stylish choice; this does not greatly affect the expressiveness of the view code, and anyone reading XML can figure out what the view is trying to achieve without looking in several places.

+4
source share

Use the @Bindable annotation and the Observable interface. It avoids template code, and you can use it for primitive data types.

 /** * Created by Amardeep on 11/2/16. */ public class Cart implements Observable { private final PropertyChangeRegistry mPropertyChangeRegistry = new PropertyChangeRegistry(); @Bindable private int itemCount; @Bindable private String name; public int getItemCount() { return itemCount; } public void setItemCount(int itemCount) { this.itemCount = itemCount; mPropertyChangeRegistry.notifyChange(this, BR.itemCount); } public String getName() { return name; } public void setName(String name) { this.name = name; mPropertyChangeRegistry.notifyChange(this, BR.name); } @Override public void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) { mPropertyChangeRegistry.add(callback); } @Override public void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) { mPropertyChangeRegistry.remove(callback); } } 
0
source share

All Articles