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