【问题标题】:How to set error on EditText using DataBinding Framework MVVM如何使用 DataBinding Framework MVVM 在 EditText 上设置错误
【发布时间】:2017-01-10 05:12:40
【问题描述】:

我正在使用 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 对象。

【问题讨论】:

    标签: android mvvm android-databinding


    【解决方案1】:

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

    方法一

    使用了数据绑定生成的最终 EditText 视图 (https://developer.android.com/topic/libraries/data-binding/index.html#views_with_ids)

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

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

    或者

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

    方法二

    如果你想使用乔治提到的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、显示的对话框。你应该使用“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 更通用,但如果我成功了,我仍然会发现更通用的方法将发布答案..
    • 我还尝试让绑定在 OOP 中更加友好。希望这能有所帮助。stackoverflow.com/questions/39283855/…
    【解决方案2】:

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

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

    使用数据绑定表达式设置属性

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

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

    使用 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 在用户键入时清除错误。数据绑定期望在调用 setter 后保留属性的值。因此,如果值没有改变,它不会再次调用 setter。因此,一旦您键入,如果计算的错误值相同,数据绑定将不会调用 setter,因此错误将消失。这种使error属性与数据绑定不兼容。

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

    【讨论】:

      【解决方案3】:

      您还可以像这样对编辑文本添加验证。

      布局文件

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

      【讨论】:

        【解决方案4】:

        我只是想分享一下我对Long Ranger for 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"
                        />
        

        【讨论】:

          猜你喜欢
          • 2021-06-22
          • 2016-05-16
          • 1970-01-01
          • 2020-07-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多