Search code examples
androidandroid-databindingandroid-architecture-componentsandroid-livedata

LiveData two-way databinding WITHOUT exposing MutableLiveData


I'm trying to use two-way databinding on a EditText, that works fine if I expose the field as MutableLiveData as it is usually seen on examples I found online.

However there are good reasons not to expose MutableLiveData and those reasons aren't magically invalid because I decided to use the databinding library.

EDIT: The main motivation here is MyViewModel should remain in control of setting data (this is the reason why it is not recommended to expose MutableLiveData directly), in the setter I can perform whatever checks or transformations necessary and then just call setValue on the LiveData.

I usually expose a LiveData getter and a separate setter from my ViewModel, I tried to get this working with two-way data binding by using the InverseMethod() annotation, but that won't really work because databinding is looking for a InverseMethod to getValue() of the LiveData itself.

Here is a simple example:

public class MyViewModel extends ViewModel {

    private MutableLiveData<String> mEmail = new MutableLiveData<>();

    // @InverseMethod("setEmail")    ### THIS DOESN'T WORK
    public LiveData<String> getEmail() {
        return mEmail;
    }

    // ### I WANT DATA-BINDING TO USE THIS METHOD
    public void setEmail(String email) {
        if (mEmail.getValue() != email) {
            mEmail.setValue(email);
        }
    }
}

and this how a want to bind it

<EditText
   android:id="@+id/input_email"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:text="@={viewmodel.email}"/>

the only workaround so far that works is using one-way data-binding to set the text on the EditText and then attach a TextWatcher and call my ViewModel.setter from there.

EDIT:
second workaround is to extend MutableLiveData and then do the checks and transformations in an overridden setValue ... that's a lot of boilerplate to write.


Solution

  • I've forgotten about this issue for a while, but as a workaround I've extended MutableLiveData slightly and use this instead every time I need control over the setter.

    import androidx.core.util.Consumer;
    import androidx.lifecycle.MutableLiveData;
    
    public class DelegatedMutableLiveData<T> extends MutableLiveData<T> {
        private Delegate<T> mDelegate;
    
        public interface Delegate<T> {
            void onSet(T value, Consumer<T> setter);
        }
    
        public DelegatedMutableLiveData(Delegate<T> delegate) {
            mDelegate = delegate;
        }
    
        public DelegatedMutableLiveData(T value, Delegate<T> delegate) {
            super(value);
            mDelegate = delegate;
        }
    
        @Override
        public void setValue(T value) {
            if (mDelegate != null) {
                mDelegate.onSet(value, super::setValue);
            } else {
                super.setValue(value);
            }
        }
    }
    

    now use DelegatedMutableLiveData as follows:

    private final DelegatedMutableLiveData<Integer> mMyLiveData = new DelegatedMutableLiveData<>((value, setter) -> {
        // do your checks and change value if necessary
        setter.accept(value); // or don't accept if you don't want to change the current value
    });