Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to bind one ObservableField to another?

I understand that the purpose of Android's data-binding library is for views to observe data and automatically update when that data changes.

Question: Is it possible for data to observe other data? For example, can I have one ObservableField "depend on" or "bind to" the value of another or a set of other ObservableFields? Currently, I have implemented this manually - every time any of the "dependee" ObservableFields change, I compute the dependent field and update its value.

Details

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

My layout file looks like this

<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());
    }
}
like image 789
curioustechizen Avatar asked Aug 06 '15 09:08

curioustechizen


People also ask

Can I use both data binding and view binding?

Data binding includes everything that ViewBinding has, so it wasn't designed to work side by side with View binding. The biggest issue is the naming conflict between the generated classes.

What is ObservableField?

It is a part of the architecture patterns of android. It is used for observing changes in the view and updating the view when it is ACTIVE.

What is an observable in Android?

An observable object can have one or more observers. An observer may be any object that implements interface Observer . After an observable instance changes, an application calling the Observable 's notifyObservers method causes all of its observers to be notified of the change by a call to their update method.


1 Answers

It is not possible to data bind two ObservableFields using binding syntax in Android data binding. However, you can bind them with 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've assumed that empty username, password, and confirmPassword are invalid. Seemed a safe assumption.

I don't see a tremendous need for private ObservableFields. ObservableField was designed to be bound to by the UI and if you can't, you can use other data types. If you find them useful for internal binding using callbacks like the above, then go for it.

like image 114
George Mount Avatar answered Sep 23 '22 10:09

George Mount