如何使用MVVM+;创建表单的字段验证;Android上的数据绑定

如何使用MVVM+;创建表单的字段验证;Android上的数据绑定,android,validation,mvvm,kotlin,android-databinding,Android,Validation,Mvvm,Kotlin,Android Databinding,我最近正在使用MVVM标准,需要在用户单击submit按钮时验证表单的字段。示例表格: <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto"> <data>

我最近正在使用MVVM标准,需要在用户单击submit按钮时验证表单的字段。示例表格:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable name="user" 
            type="me.example.presentation.model.User" />
        <variable
            name="presenter"
            type="me.example.presentation.view.LoginActivity"/>

... <!-- some code -->

    <EditText
        android:id="@+id/etPassword"
        android:layout_width="match_parent"                           
        android:layout_height="wrap_content"                          
        android:digits="@string/allowed_digits_vehicle_plate"                   
        android:hint="@string/login_hint_vehicle_plate"
        android:inputType="textFilter|textCapCharacters"
        android:maxLength="7"
        android:text="@={user.password}"
        password="@{user.password}"
        android:textSize="@dimen/size16" />

... <!-- some code -->

    <Button
        android:id="@+id/btEnter"
        android:layout_width="match_parent"
        android:layout_height="@dimen/login_button_enter"
        android:layout_marginTop="@dimen/margin_16dp"
        android:layout_marginBottom="@dimen/margin_8dp"
        android:text="@string/enter"
        android:onClick="@{() -> presenter.onLoginClick()}"/>

... <!-- some code -->



... 
... 
... 
我正在尝试使用绑定适配器验证edittext,如下所示:

@JvmStatic
@BindingAdapter("password")
fun setPassError(editText: EditText, pass: String) {
    if (pass.isEmpty()) {
       editText.error = null
       return
    }

    if (editText.text.toString().length  < 7) {
       editText.error = "invalid pass"
    } else {
       editText.error = null
    }
}
@JvmStatic
@BindingAdapter(“密码”)
fun setPassError(editText:editText,pass:String){
if(pass.isEmpty()){
editText.error=null
返回
}
if(editText.text.toString().length<7){
editText.error=“无效传递”
}否则{
editText.error=null
}
}

这样,它在用户键入时进行验证,但我希望它在单击submit按钮时执行验证。如何更改和改进此方法?

您可以将文本监视程序添加到编辑文本中,然后将编辑文本保存在模型的字符串字段中,然后单击按钮使用它

class Model{
   private TextWatcher textWatcher;
   private String text;

   public Model() {
       this.textWatcher = new TextChangeWatcher() {
           @Override
           public void onTextChanged(CharSequence s, int start, int before, int count) {
               text= s.toString();
           }
       };

   }

   public void btnClick() {
    //now you can validate string text here
   }
}


要将文本观察程序添加到编辑文本中,您可以使用此答案。

您可以将文本观察程序添加到编辑文本中,然后将编辑文本保存在模型的字符串字段中,然后单击按钮使用它

class Model{
   private TextWatcher textWatcher;
   private String text;

   public Model() {
       this.textWatcher = new TextChangeWatcher() {
           @Override
           public void onTextChanged(CharSequence s, int start, int before, int count) {
               text= s.toString();
           }
       };

   }

   public void btnClick() {
    //now you can validate string text here
   }
}


要添加文本观察者来编辑文本,您可以使用此答案。

我个人会像您一样保留双向绑定和单击处理。我不需要绑定适配器,因为数据绑定库可以找出方法
setError

在XML上,我会将错误分配给一个可观察的变量,该变量可能位于
presenter
变量中(我知道您说的是mvvm,但XML称之为presenter)


现在,在单击侦听器中,您可以添加验证逻辑

val passwordError = ObservableField<String>()

fun btnClick() {
  if (user.pass.isEmpty()) {
    passwordError.set("Empty password")
    return
  }

  if (user.pass.length  < 7) {
    passwordError.set( "nvalid pass")
  } else {
    passwordError.set(null)
  }
}
val passwordError=observeField()
有趣的事{
if(user.pass.isEmpty()){
密码错误。设置(“空密码”)
返回
}
如果(user.pass.length<7){
passwordError.set(“nvalid pass”)
}否则{
passwordError.set(空)
}
}

我假设您可以从presenter访问用户变量。尽管如此,我认为这已经足够让我明白这一点。

就我个人而言,我会像您一样保留双向绑定和点击处理。我不需要绑定适配器,因为数据绑定库可以找出方法
setError

在XML上,我会将错误分配给一个可观察的变量,该变量可能位于
presenter
变量中(我知道您说的是mvvm,但XML称之为presenter)


现在,在单击侦听器中,您可以添加验证逻辑

val passwordError = ObservableField<String>()

fun btnClick() {
  if (user.pass.isEmpty()) {
    passwordError.set("Empty password")
    return
  }

  if (user.pass.length  < 7) {
    passwordError.set( "nvalid pass")
  } else {
    passwordError.set(null)
  }
}
val passwordError=observeField()
有趣的事{
if(user.pass.isEmpty()){
密码错误。设置(“空密码”)
返回
}
如果(user.pass.length<7){
passwordError.set(“nvalid pass”)
}否则{
passwordError.set(空)
}
}

我假设您可以从presenter访问用户变量。尽管如此,我认为这已经足够让人产生想法。

我这样做是为了检查edittext字段:

// I created a LiveData to observe the validations inside my ViewModel.
private val validationLiveEvent = LiveEvent<Validations>()
val validation: LiveData<Validations> = validationLiveEvent

fun validate (user: User) : Boolean {

   if (user.email.trim { it <= ' ' }.isEmpty() 
       || user.email.trim { it <= ' ' }.length < 6) {

       // I put the value so that the screen has some action
       validationLiveEvent.value = Validations.EmailEmpty
       return false
   }

   if (user.password.trim { it <= ' ' }.isEmpty()) {
       validationLiveEvent.value = Validations.PasswordEmpty
       return false
   }

   return true
}
通过观察活动中的验证,我可以说出应在屏幕上显示的消息:

loginViewModel.validation.observe(this, Observer {

    when(it) {
        Validations.EmailEmpty -> {
            binding.etEmail.error = getString(R.string.login_hint_email_error)
            binding.etEmail.focus()
        }

        Validations.PasswordEmpty -> {
            binding.tilPassword.isPasswordVisibilityToggleEnabled = false
            binding.etPassword.error = getString(R.string.login_password_hint)
            binding.etPassword.focus()
        }
    }
})

我认为有几种方法可以做到,请随意发布更多答案。我喜欢所有的方法,并将对它们进行测试。Tks

我这样做是为了检查edittext字段:

// I created a LiveData to observe the validations inside my ViewModel.
private val validationLiveEvent = LiveEvent<Validations>()
val validation: LiveData<Validations> = validationLiveEvent

fun validate (user: User) : Boolean {

   if (user.email.trim { it <= ' ' }.isEmpty() 
       || user.email.trim { it <= ' ' }.length < 6) {

       // I put the value so that the screen has some action
       validationLiveEvent.value = Validations.EmailEmpty
       return false
   }

   if (user.password.trim { it <= ' ' }.isEmpty()) {
       validationLiveEvent.value = Validations.PasswordEmpty
       return false
   }

   return true
}
通过观察活动中的验证,我可以说出应在屏幕上显示的消息:

loginViewModel.validation.observe(this, Observer {

    when(it) {
        Validations.EmailEmpty -> {
            binding.etEmail.error = getString(R.string.login_hint_email_error)
            binding.etEmail.focus()
        }

        Validations.PasswordEmpty -> {
            binding.tilPassword.isPasswordVisibilityToggleEnabled = false
            binding.etPassword.error = getString(R.string.login_password_hint)
            binding.etPassword.focus()
        }
    }
})

我认为有几种方法可以做到,请随意发布更多答案。我喜欢所有的方法,并将对它们进行测试。Tks

我编写了自己的构建器,以便构建一个LiveData,它依赖于一些给定的约束和其他LiveData实例来用作触发器。见下文

/**
*用于创建{@link LiveData}实例的生成器类,该实例组合了多个
*源(触发器)并计算要最终发出的给定约束
*{@code true}(成功/有效)或{@code false}(失败/无效),这是一个
*使用{@code AND}运算符聚合所有约束。
*/
公共最终类验证器LiveDataBuilder{
/**
*支持使用{@code AND}操作聚合供应商的布尔供应商。
*/
私有静态最终类BooleanAndSupplier实现BooleanSupplier{
/**
*源{@code supplier}的字段。
*/
私人最终供应商来源;
/**
*私有构造函数
*
*@param source此供应商的基础
*/
私人Boolean供应商(Boolean供应商来源){
this.source=源;
}
/**
*返回一个新的{@code supplier},它结合了{@code this}实例
*以及给定的供应商。
*注意:如果{@code this}实例,则不调用给定的{@code supplier} *计算结果为{@code false}。 * *@param supplier要与之合并的供应商 *@返回一个新的组合{@code BooleanAndSupplier} */ 私人布尔人和供应商(布尔人供应商){ 返回新的BooleanAndSupplier(()->{ 如果(!getAsBoolean()){ 返回false; } 返回supplier.getAsBoolean(); }); } @凌驾 公共布尔getAsBoolean(){ 返回source.getAsBoolean(); } } /** *返回的{@link LiveData}的字段。 */ private final MediatorLiveData validatorLiveData=新MediatorLiveData(); /** *所用验证程序的字段。 */ 私有BooleanAndSupplier validator=新的BooleanAndSupplier(()->true); /** *所有添加的源的字段。 */
private final List我编写了自己的构建器,以构建一个LiveData,它依赖于一些给定的约束和其他LiveData实例来用作触发器。请参见下文

/**
*用于创建{@link的生成器类
notifyPropertyChanged(BR.requestBalanceCommand)
public LiveData<Boolean> getValidator() {
    return validator;
}
<Button command="@{vm.requestBalanceCommand}"
        command_validator="@{vm.validator}"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Request Balance" />
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="loginViewModel"
            type="com.xxx.android.ui.customer.login.LoginViewModel" />

        <variable
            name="customerViewModel"
            type="com.xxx.android.ui.customer.CustomerViewModel" />

    </data>

    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <LinearLayout
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/edtEmail"
            android:layout_marginTop="40dp"
            android:layout_marginStart="24dp"
            android:layout_marginEnd="24dp"
            android:textSize="14sp"
            android:inputType="textEmailAddress"
            android:hint="Enter email"
            android:onTextChanged="@{loginViewModel.onEmailChanged}"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/edtPassword"
            android:layout_marginStart="24dp"
            android:layout_marginEnd="24dp"
            android:layout_marginTop="12dp"
            android:textSize="14sp"
            android:inputType="textPassword"
            android:hint="Enter password"
            android:onTextChanged="@{loginViewModel.onPasswordChanged}"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>


        <androidx.appcompat.widget.AppCompatButton
            android:layout_marginTop="24dp"
            android:background="@android:color/holo_orange_dark"
            android:textColor="@color/white"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:enabled="@{ loginViewModel.isEmailValidate &amp; loginViewModel.isPasswordValidate() ? true : false  }"

            android:padding="12dp"
            android:text="@string/logout" />



    </LinearLayout>

    </androidx.core.widget.NestedScrollView>
</layout>
private var _isEmailValidate = MutableLiveData<Boolean>()
val isEmailValidate: LiveData<Boolean>
get() = _isEmailValidate

private var _isPasswordValidate = MutableLiveData<Boolean>()
val isPasswordValidate: LiveData<Boolean>
get() = _isPasswordValidate

fun onEmailChanged(s: CharSequence, start: Int, before: Int, count: Int) {
    val email = s.toString()
    _isEmailValidate.postValue(android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches())
    Log.e("Frank", "onEmailChanged ${s.toString()}")

}

fun onPasswordChanged(s: CharSequence, start: Int, before: Int, count: Int) {
    Log.e("Frank", "onEmailChanged ${s.toString()}")
    val result = (s.length > 0)
    _isPasswordValidate.postValue(result)
}