问题描述
我正在尝试使用 MVVM 架构构建表单验证机制。我为此使用了三个类:
表单很简单,包含电子邮件和密码字段。
但是,视图中的更改不会反映回 POJO 对象。这是我的布局文件
layout_login.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="loginViewModel"
type="com.example.increaseyouriq.ui.forms.LoginFormViewModel" />
</data>
<LinearLayout
android:id="@+id/ll_login"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/parent.contentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="26dp">
<com.google.android.material.card.MaterialCardView
style="@style/cardOutline"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
style="@style/viewParent.headerText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/login_title" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
style="@style/textInputStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/enter_email"
error="@{loginViewModel.loginForm.emailError}"
app:errorEnabled="@{loginViewModel.loginForm.isEmailError()}"
app:endIconMode="clear_text">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_login_email"
style="@style/textInputEditext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
onFocus="@{loginViewModel.onFocusEmailChangeListener}"
android:text="@={loginViewModel.loginFields.email}"
android:inputType="textNoSuggestions|textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/textInputStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/enter_password"
error="@{loginViewModel.loginForm.passwordError}"
app:errorEnabled="@{loginViewModel.loginForm.isPasswordError()}"
app:endIconMode="password_toggle">
<com.google.android.material.textfield.TextInputEditText
style="@style/textInputEditext"
android:id="@+id/et_login_password"
onFocus="@{loginViewModel.onFocusPasswordChangeListener}"
android:text="@={loginViewModel.loginFields.password}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_login_skip"
style="@style/myMaterialButton.Unboxed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/skip"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toLeftOf="@id/btn_login_login" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_login_login"
style="@style/myMaterialButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pay_deail_submit_btn"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<View
android:layout_width="wrap_content"
android:layout_height="1dp" />
</LinearLayout>
</layout>
layout_login 登录Activity.java
public class LoginActivity extends AppCompatActivity {
ActivityLoginBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityLoginBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.setLifecycleOwner(this);
LoginFormViewModel loginFormViewModel = new ViewModelProvider(this).get(LoginFormViewModel.class);
loginFormViewModel.init();
binding.setLoginViewModel(loginFormViewModel);
loginFormViewModel.getLoginFields().observe(this,loginFields -> {
Toast.makeText(LoginActivity.this,"Email " + loginFields.getEmail() + ",Password " + loginFields.getPassword(),Toast.LENGTH_SHORT).show();
});
binding.inLayoutLogin.btnLoginLogin.setOnClickListener(view -> {
binding.executePendingBindings();
loginFormViewModel.getLoginForm().onClick();
});
}
}
layout_login.xml 在 activity_login 中被引用如下: login_activity.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="loginViewModel"
type="com.example.increaseyouriq.ui.forms.LoginFormViewModel" />
</data>
<FrameLayout
style="@style/parent.contentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary">
<LinearLayout
style="@style/parent.contentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:orientation="vertical">
<include
android:id="@+id/in_layout_login"
layout="@layout/layout_login"
bind:loginViewModel="@{loginViewModel}" />
<include
android:id="@+id/in_layout_register"
layout="@layout/layout_register"
android:visibility="gone" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</FrameLayout>
</layout>
LoginActivity.java
public class LoginActivity extends AppCompatActivity {
ActivityLoginBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityLoginBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.setLifecycleOwner(this);
LoginFormViewModel loginFormViewModel = new ViewModelProvider(this).get(LoginFormViewModel.class);
loginFormViewModel.init();
binding.setLoginViewModel(loginFormViewModel);
loginFormViewModel.getLoginFields().observe(this,Toast.LENGTH_SHORT).show();
});
binding.inLayoutLogin.btnLoginLogin.setOnClickListener(view -> {
binding.executePendingBindings();
loginFormViewModel.getLoginForm().onClick();
});
}
}
LoginActivity 只需要为按钮设置 ViewHolder 和点击监听器即可。
要了解 ViewModel,我们需要了解这三个类:
- LoginFields.java
- LoginErrorFields.java
- 登录表单.java
LoginFields.java
public class LoginFields {
protected String email;
protected String password;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
LoginErrorFields.java
public class LoginErrorFields {
private Integer email;
private Integer password;
public Integer getEmail() {
return email;
}
public void setEmail(Integer email) {
this.email = email;
}
public Integer getPassword() {
return password;
}
public void setPassword(Integer password) {
this.password = password;
}
}
显然LoginFields 和LoginErrorFields 是POJO 类。 LoginFields 存储电子邮件和密码,而 LoginErrorFields 存储与其相关的错误。
LoginForm.java
public class LoginForm extends BaseObservable {
private LoginFields fields = new LoginFields();
private LoginErrorFields errors = new LoginErrorFields();
private final MutableLiveData<LoginFields> buttonClick = new MutableLiveData<>();
@Bindable
public boolean isValid() {
boolean valid = isEmailValid(false);
valid = isPasswordValid(false) && valid;
notifyPropertyChanged(BR.emailError);
notifyPropertyChanged(BR.passwordError);
return valid;
}
public boolean isEmailValid(boolean setMessage) {
// Minimum [email protected]
String email = fields.getEmail();
if (email != null && email.length() > 5) {
if (true) {
errors.setEmail(null);
notifyPropertyChanged(BR.valid);
return true;
} else {
if (setMessage) {
errors.setEmail(R.string.invalid_email_error);
notifyPropertyChanged(BR.valid);
}
return false;
}
}
if (setMessage) {
errors.setEmail(R.string.error_too_short);
notifyPropertyChanged(BR.valid);
}
return false;
}
public boolean isPasswordValid(boolean setMessage) {
String password = fields.getPassword();
if (password != null && password.length() > 5) {
errors.setPassword(null);
notifyPropertyChanged(BR.valid);
return true;
} else {
if (setMessage) {
errors.setPassword(R.string.error_too_short);
notifyPropertyChanged(BR.valid);
}
return false;
}
}
public void onClick() {
if (isValid()) {
buttonClick.setValue(fields);
}
}
public MutableLiveData<LoginFields> getLoginFields() {
return buttonClick;
}
public LoginFields getFields() {
return fields;
}
@Bindable
public Integer getEmailError() {
return errors.getEmail();
}
@Bindable
public Integer getPasswordError() {
return errors.getPassword();
}
@Bindable
public boolean isEmailError() {
return errors.getEmail()!=null;
}
@Bindable
public boolean isPasswordError() {
return errors.getPassword()!=null;
}
}
现在,LoginForm 结合两个 POJO 验证来自 LoginFields 的输入并在 LoginErrorFields 中设置错误。它还包含一个实时数据,如果没有错误,则设置其值。
现在我们终于有了一个使用 LoginForm 将 OnFocusListeners 和 Errors 设置为相应视图的 ViewModel。 LoginFormViewModel.java
public class LoginFormViewModel extends ViewModel {
private LoginForm loginForm;
private View.OnFocusChangeListener onFocusEmailChangeListener;
private View.OnFocusChangeListener onFocusPasswordChangeListener;
public void init() {
loginForm = new LoginForm();
onFocusEmailChangeListener= (v,hasFocus) -> {
TextInputEditText et = (TextInputEditText) v;
if (et.getText().length() > 0 && !hasFocus) {
loginForm.isEmailValid(true);
}
};
onFocusPasswordChangeListener=(v,hasFocus) -> {
TextInputEditText et = (TextInputEditText) v;
if (et.getText().length() > 0 && !hasFocus) {
loginForm.isPasswordValid(true);
}
};
}
public LoginForm getLoginForm() {
return loginForm;
}
public View.OnFocusChangeListener getOnFocusEmailChangeListener() {
return onFocusEmailChangeListener;
}
public View.OnFocusChangeListener getOnFocusPasswordChangeListener() {
return onFocusPasswordChangeListener;
}
public MutableLiveData<LoginFields> getLoginFields(){
return loginForm.getLoginFields();
}
@BindingAdapter("error")
public static void setError(TextInputLayout editText,Object strOrResId) {
if (strOrResId instanceof Integer) {
editText.setError(
editText.getContext().getString((Integer) strOrResId));
} else {
editText.setError((String) strOrResId);
}
}
@BindingAdapter("onFocus")
public static void bindFocusChange(TextInputEditText editText,View.OnFocusChangeListener onFocusChangeListener) {
if (editText.getOnFocusChangeListener() == null) {
editText.setOnFocusChangeListener(onFocusChangeListener);
}
}
}
现在,如果您忘记了问题所在,则是 et_login_email 和 et_login_password 的 text 属性未更新为 LoginFields 对象(包含在 LoginForms 中,该对象包含在 LoginFormViewModel 中),我有使用调试并确认当 UI 数据更改时电子邮件和密码字段都为空。
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)