Как установить ошибку в EditText с помощью DataBinding Framework MVVM

Я использую структуру привязки данных Android. Я предполагаю, что EditText для формы входа в систему с именем пользователя, как показано ниже

<EditText
        android:id="@+id/etext_uname"
        style="@style/login_edittext"
        android:hint="@string/hint_username"
        android:inputType="textEmailAddress" />

Я также определил LoginViewModel, но мне нужна помощь, как установить ошибку в тексте редактирования, когда пользователь вводит неправильный адрес электронной почты в каком-то событии, скажем, внутри

public void afterTextChanged(@NonNull final Editable editable)

Потому что, насколько мне известно, в традиционном подходе к Android мы можем сделать это программно с помощью метода et.setError(), но я не хочу создавать объект edittext с помощью Activity или Fragment.


person Pranav    schedule 02.09.2016    source источник


Ответы (4)


Если вы хотите сделать что-то вроде функции EditText.setError() с привязкой данных, вот два метода.

Способ 1

Используется окончательное представление EditText, сгенерированное из привязки данных (https://developer.android.com/topic/libraries/data-binding/index.html#views_with_ids)

Вы можете вызвать EditText напрямую, не создавая его вручную, так как он автоматически генерируется после того, как вы установите идентификатор для представления (также верно для включенного макета) .

MainActivityBinding.etext_uname.setError("Wrong email format");

Or

MainActivityBinding.etext_uname.addTextChangedListener(new MyOwnTextWatcher());

Метод 2

Если вы хотите использовать метод привязки с xml, как упоминал Джордж (https://medium.com/google-developers/android-data-binding-custom-setters-55a25a7aea47#.su88ujqrn)

Сначала вы должны установить свой собственный метод привязки. Предложите создать еще один класс для всех методов привязки.

Метод должен быть статическим, с аннотацией @BindingAdapter и соответствующим именем метода привязки (пространство имен и имя метода можно настроить)

<сильный>1. Установите пользовательский TextWatcher

public class MyOwnBindingUtil {
    public interface StringRule {
        public boolean validate(Editable s);
    }
    @BindingAdapter("android:watcher")
    public static void bindTextWatcher(EditText pEditText, TextWatcher pTextWatcher) {
        pEditText.addTextChangedListener(pTextWatcher);
    }
    @BindingAdapter(value = {"email:rule", "email:errorMsg"}, requireAll = true)
    public static void bindTextChange(final EditText pEditText, final StringRule pStringRule, final String msg) {
        pEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                }
                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                }
                @Override
                public void afterTextChanged(Editable s) {
                    if (!pStringRule.validate(s)) {
                        pEditText.setError(msg);
                }
            }
        });
    }
    /*
    Your other custom binding method
     */
}

Если вы хотите настроить свой собственный TextWatcher с настраиваемым действием, например показано тост, показано диалоговое окно. Вы должны использовать метод «android: watcher»

mBinding.setWatcher(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    }
    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
    }
    @Override
    public void afterTextChanged(Editable s) {
    }
});

В xml,

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:email="http://schemas.android.com/tools"
    >

    <data>
        <variable
            name="watcher"
            type="android.text.TextWatcher"/>
        <variable
            name="emailRule"
            type="example.com.testerapplication.MyOwnBindingUtil.StringRule"/>
        <variable
            name="errorMsg"
            type="java.lang.String"/>
    </data>
    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="Input Email"
        android:watcher="@{watcher}
        />

<сильный>2. Настройте собственное правило проверки и сообщение об ошибке

Если вы хотите использовать функцию setError и оставили для настройки только errorMsg и логику проверки. Вы можете установить xml следующим образом.

В xml,

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:email="http://schemas.android.com/tools"
    >

    <data>
        <variable
            name="watcher"
            type="android.text.TextWatcher"/>
        <variable
            name="emailRule"
            type="example.com.testerapplication.MyOwnBindingUtil.StringRule"/>
        <variable
            name="errorMsg"
            type="java.lang.String"/>
    </data>
    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="Input Email"
        email:rule="@{emailRule}"
        email:errorMsg="@{errorMsg}"
        />

Код активности

mBinding.setErrorMsg("Wrong type");
mBinding.setEmailRule(new MyOwnBindingUtil.StringRule() {
    @Override
    public boolean validate(Editable s) {
        // check if the length of string is larger than 18  
        return s.toString().length() > 18;
    }
});

Пожалуйста, не стесняйтесь редактировать мой код, чтобы сделать привязку более универсальной для использования разработчиком.

person Long Ranger    schedule 05.09.2016
comment
Хорошее объяснение + 1 для ответа. Я думаю, что метод 2 более общий, но я все еще нахожу, что более общий подход опубликует ответ, если я добьюсь успеха. - person Pranav; 05.09.2016
comment
Я также стараюсь сделать привязку более дружественной к ООП. Надеюсь, что это может помочь. "> stackoverflow.com/questions/39283855/ - person Long Ranger; 06.09.2016

По сути, вам нужен способ реализации зависимых полей. Ошибка зависит от значения текста. Вы хотите, чтобы значение ошибки обновлялось при изменении текста.

Я нашел два способа добиться этого:

Установить атрибут с помощью выражения привязки данных

<EditView
    android:text="@={viewModel.email}"
    android:error="@={viewModel.emailRule.check(email)} />

Привязка данных гарантирует, что функция check вызывается всякий раз, когда изменяется email.

Используйте RxJava для преобразования одного поля в другое.

Я написал утилиту для преобразования между ObservableField и Observable. См. FieldUtils.java

Используя это, вы можете реализовать в своем коде ViewModel/Model.

public class ViewModel {
    ObservableField<String> email = new ObservableField<>();
    ObservableField<String> emailError = toField(toObservable(email).map(new Func1<String, String>() {
            @Override
            public String call(String email) {
                return FormUtils.checkEmail(email) ? null : "Invalid Email";
            }
        }));
}

Проблема с EditText

EditText очищает ошибку при вводе пользователем. Привязка данных предполагает, что значение атрибута сохраняется после вызова установщика. Таким образом, он не вызывает снова сеттер, если значение не изменяется. Следовательно, как только вы наберете, если вычисленное значение ошибки будет таким же, привязка данных не будет вызывать установщик, и, следовательно, ошибка исчезнет. Этот вид делает атрибут error несовместимым с привязкой данных.

Я предпочитаю использовать TextInputLayout, предоставляемый библиотекой дизайна. Он имеет постоянное поле ошибки, а также выглядит лучше.

person Manas Chaudhari    schedule 11.10.2016

Я просто хочу поделиться своей модификацией ответа Long Ranger для Android Arch ViewModel:

    public class StringValidationRules {

    public static StringRule NOT_EMPTY = new StringRule() {
        @Override
        public boolean validate(Editable s) {
            return TextUtils.isEmpty(s.toString());
        }
    };

    public static StringRule EMAIL = new StringRule() {
        @Override
        public boolean validate(Editable s) {
            return !android.util.Patterns.EMAIL_ADDRESS.matcher(s).matches();

        }
    };

    public static StringRule PASSWORD = new StringRule() {
        @Override
        public boolean validate(Editable s) {
            return s.length() < 8;
        }
    };

    public interface StringRule {
        boolean validate(Editable s);
    }
}

модель представления...

    public class LoginViewModel extends ViewModel {
...
@BindingAdapter({"app:validation", "app:errorMsg"})
    public static void setErrorEnable(EditText editText, StringValidationRules.StringRule stringRule, final String errorMsg) {
        editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                if (stringRule.validate(editText.getText())) {
                    editText.setError(errorMsg);
                } else {
                    editText.setError(null);
                }
            }
        });
    }

...

и XML:

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:bind="http://schemas.android.com/apk/res-auto"
    >
    <data>
        <variable name="viewModel" type="com.fernandonovoa.sapmaterialstockoverview.login.LoginViewModel"/>
        <import type="com.fernandonovoa.sapmaterialstockoverview.utils.StringValidationRules" />
    </data>

...

<EditText
                android:id="@+id/etEmail"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Ingrese su email"
                android:inputType="textEmailAddress"
                android:drawableLeft="@drawable/ic_email"
                android:drawableStart="@drawable/ic_email"
                app:validation="@{StringValidationRules.EMAIL}"
                app:errorMsg='@{"Email no válido"}'
                style="@style/AppTheme.Widget.TextInputLayoutLogin"
                />

<EditText
                android:id="@+id/etPassword"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Ingrese su contraseña"
                android:inputType="textPassword"
                android:drawableLeft="@drawable/ic_lock"
                android:drawableStart="@drawable/ic_lock"
                app:validation="@{StringValidationRules.PASSWORD}"
                app:errorMsg='@{"Contraseña no válida"}'
                style="@style/AppTheme.Widget.TextInputLayoutLogin"
                />
person Fernando Novoa Carbajal    schedule 07.08.2018

Вы также можете добавить проверку на текст редактирования, как это.

Файл макета

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewModel"
            type="com.example.app.ui.login.LoginViewModel" />

        <import type="com.example.app.ui.ValidationRule" />

        <variable
            name="watcher"
            type="android.text.TextWatcher" />

        <import type="com.example.app.utils.ValidationUtils" />

    </data>

    <RelativeLayout
        android:id="@+id/login"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp"
        tools:context=".ui.login.LoginFragment">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:orientation="vertical">

            <com.google.android.material.textfield.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="username"
                android:watcher="@{watcher}"
                app:error="@{@string/validation_error_msg_email}"
                app:rule="@{ValidationRule.EMPTY}">

                <com.google.android.material.textfield.TextInputEditText
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@={viewModel.usernameObs}" />
            </com.google.android.material.textfield.TextInputLayout>


            <com.google.android.material.textfield.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="password"
                android:watcher="@{watcher}"
                app:error="@{@string/validation_error_msg_password}"
                app:rule="@{ValidationRule.PASSWORD}">

                <com.google.android.material.textfield.TextInputEditText
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:inputType="textPassword"
                    android:text="@={viewModel.passwordObs}" />
            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.button.MaterialButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="16dp"
                android:background="?colorAccent"
                android:enabled="@{ValidationUtils.isValidEmail(viewModel.usernameObs) &amp;&amp; ValidationUtils.isValidPassword(viewModel.passwordObs)}"
                android:onClick="@{() -> viewModel.login()}"
                android:text="Login"
                android:textColor="?android:textColorPrimaryInverse" />
        </LinearLayout>
    </RelativeLayout>
</layout>

BindingUtils

object BindingUtils {
        @BindingAdapter(value = ["error", "rule", "android:watcher"], requireAll = true)
        @JvmStatic
        fun watcher(textInputLayout: com.google.android.material.textfield.TextInputLayout, errorMsg: String, rule: ValidationRule, watcher: TextWatcher) {
            textInputLayout.editText?.addTextChangedListener(object : TextWatcher {
                override fun afterTextChanged(p0: Editable?) {
                }

                override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
                }

                override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
                    textInputLayout.error = null
                    if (rule == ValidationRule.EMPTY && !ValidationUtils.isValidEmail(p0.toString())) textInputLayout.error = errorMsg
                    if (rule == ValidationRule.PASSWORD && !ValidationUtils.isValidPassword(p0.toString())) textInputLayout.error = errorMsg
                }
            })
        }
    }

Правило проверки

enum class ValidationRule{
    EMPTY, EMAIL, PASSWORD
}

Не забудьте установить наблюдателя во фрагменте или активности, как это

binding.watcher = object : TextWatcher {
        override fun afterTextChanged(p0: Editable?) {
        }

        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        }

        override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        }
    }
person Rajesh Khadka    schedule 03.07.2018