如何使用DataBinding Framework MVVM在EditText上设置错误

时间:2016-09-02 07:15:34

标签: android mvvm android-databinding

我正在使用Android数据绑定框架我假设一个EditText用于登录表单,用户名如下

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

我也定义了LoginViewModel但我需要帮助如何在用户输入错误的电子邮件地址时在edittext中设置错误在某些情况下让我们说内部

public void afterTextChanged(@NonNull final Editable editable)

因为据我所知,在传统Android方法中,我们可以通过et.setError()方法以编程方式执行此操作,但我不想通过Activity或Fragment创建edittext对象。

4 个答案:

答案 0 :(得分:6)

如果你想用数据绑定做EditText.setError()函数,这里有两种方法。

方法1

使用从数据绑定(https://developer.android.com/topic/libraries/data-binding/index.html#views_with_ids

生成的最终EditText视图

您可以直接调用EditText而无需手动创建,因为在为视图设置ID后会自动生成它(对于包含的布局也是如此)

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

或者

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,如显示的Toast,则显示Dialog。你应该使用“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;
    }
});

请随意编辑我的代码,以使开发人员使用绑定更通用。

答案 1 :(得分:2)

从根本上说,您需要一种方法来实现依赖字段。错误取决于文本的值。您希望在文本更改时更新错误值。

我找到了两种方法来实现这一目标:

使用数据绑定表达式

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

数据绑定可确保在check更改时调用email函数。

使用RxJava从一个字段转换为另一个字段

我编写了一个实用程序,可以在ObservableFieldObservable之间进行转换。见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会在用户输入时清除错误。 Data Binding期望在调用setter之后保留属性的值。因此,如果值没有改变,它不会再次调用setter。因此,只要键入,如果计算的错误值相同,数据绑定将不会调用setter,因此错误将消失。这种使error属性与数据绑定不兼容。

我更喜欢使用设计库提供的TextInputLayout。它有一个持久的错误字段,看起来也更好。

答案 2 :(得分:2)

我只想分享我对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);
    }
}

viewModel ...

    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"
                />

答案 3 :(得分:0)

您还可以在这样的编辑文本上添加验证。

布局文件

<?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
                }
            })
        }
    }

ValidationRule

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) {
        }
    }