Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 510
Su-Au Hwang Avatar asked May 28 '19 11:05

Su-Au Hwang


People also ask

What is the difference between MutableLiveData and LiveData?

By using LiveData we can only observe the data and cannot set the data. MutableLiveData is mutable and is a subclass of LiveData. In MutableLiveData we can observe and set the values using postValue() and setValue() methods (the former being thread-safe) so that we can dispatch values to any live or active observers.

How LiveData is different from ObservableField?

ObservableField is not Lifecycle aware but LiveData is lifecycle aware and hence will notify only the observables which are “active”. We have to do manual handling of Lifecycle awareness in ObservableField.

When should I use DataBinding?

If your project is however more complex and you need to add features like binding data to views, binding adapters e.t.c, use DataBinding.


2 Answers

We have been recommended to switch from ObservableField to LiveData for data binding because it is lifecycle-aware. We have also been recommended not expose MutableLiveData because the view model should control assignment.

This works perfectly for one-way data binding and in those cases I would only expose LiveData.

We want to use two-way data binding, which by definition moves assignment from the view model to the UI, so I think in this case exposing MutableLiveData is correct. I say this as we are doing it on purpose because we want our UI to be able to assign values so that we have cleaner views.

like image 124
Nic Bell Avatar answered Sep 29 '22 01:09

Nic Bell


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
});
like image 35
Su-Au Hwang Avatar answered Sep 29 '22 01:09

Su-Au Hwang