如何使用MVVM+;创建表单的字段验证;Android上的数据绑定
我最近正在使用MVVM标准,需要在用户单击submit按钮时验证表单的字段。示例表格:如何使用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>
<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 & 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)
}