Я пытаюсь использовать двустороннюю привязку данных к EditText, которая отлично работает, если я выставляю поле как MutableLiveData, как это обычно видно в примерах, которые я нашел в Интернете.
Однако есть веские причины не раскрывать MutableLiveData, и эти причины не являются волшебным образом недействительными, потому что я решил использовать библиотеку привязки данных.
MutableLiveData напрямую), в установщике я могу выполнять любые необходимые проверки или преобразования, а затем просто вызывать setValue на LiveData .Обычно я выставляю геттер LiveData и отдельный сеттер из моей ViewModel. Я пытался заставить это работать с двусторонней привязкой данных с помощью аннотации InverseMethod(), но на самом деле это не сработает, потому что привязка данных ищет InverseMethod для getValue() Сам LiveData.
Вот простой пример:
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);
}
}
}
и вот как я хочу его связать
<EditText
android:id = "@+id/input_email"
android:layout_width = "match_parent"
android:layout_height = "wrap_content"
android:text = "@ = {viewmodel.email}"/>
пока единственный обходной путь, который работает, - это использование односторонней привязки данных для установки текста в EditText, а затем присоединения TextWatcher и вызова моего ViewModel.setter оттуда.
Обновлено:
второй обходной путь - расширить MutableLiveData, а затем выполнить проверки и преобразования в переопределенном setValue ... это много шаблонного кода для написания.
У меня был точно такой же вопрос, вот как я нашел ваш. Я знаю, что это не совсем то, что вы ищете, но можно было бы наблюдать за mEmail из вашей ViewModel и реализовать в нем свой код setEmail() (после того, как само значение было установлено, конечно... я не знаю, как управлять установкой значения, которое, я думаю, вы ищете)
val observer = Observer<String> { setEmail(it)}
fun setEmail(value:String){ //Your code }
init{
mEmail.observeForever(observer)
}
//Don´t forget to remove the observer
Спасибо Gio за отзыв, проблема с этим методом в том, что все остальные наблюдатели, по крайней мере, временно увидят недопустимое значение.
Нам рекомендовали переключиться с ObservableField на LiveData для привязки данных, поскольку он учитывает жизненный цикл. Нам также рекомендовали не раскрывать MutableLiveData, потому что модель представления должна управлять назначением.
Это отлично работает для односторонней привязки данных, и в этих случаях я бы открывал только LiveData.
Мы хотим использовать двустороннюю привязку данных, которая по определению перемещает назначение из модели представления в пользовательский интерфейс, поэтому я думаю, что в этом случае раскрытие MutableLiveData правильно. Я говорю это, потому что мы делаем это специально, потому что мы хотим, чтобы наш пользовательский интерфейс мог назначать значения, чтобы у нас были более чистые представления.
Проблема остается прежней, вы теряете контроль над сеттером. Например, в вашем пользовательском интерфейсе есть EditText, и вы хотите ограничить допустимый тип текста. Теперь вам понадобится еще один TextWatcher, чтобы сделать это.
Я забыл об этой проблеме на какое-то время, но в качестве обходного пути я немного расширил MutableLiveData и использую его вместо этого каждый раз, когда мне нужен контроль над сеттером.
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);
}
}
}
теперь используйте DelegatedMutableLiveData следующим образом:
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
});
У меня такая же проблема. Именно так я хотел бы сделать это, но я не знаю. Обходные пути требуют слишком много накладных расходов, поэтому я собираюсь раскрыть MutabeLiveData, когда буду выполнять двухстороннюю привязку данных. Было бы неплохо, если бы было лучшее решение. В сообщении об ошибке говорится:
There is no inverse for method getValue, you must add an @InverseMethod annotation to the method to indicate which method should be used when using it in two-way binding expressions