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.
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.
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.
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.
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.
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
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With