数据绑定:查看不更新可观察内部类中的变量

问题描述

我正在尝试使用 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.xmlactivity_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,我们需要了解这三个类:

  1. LoginFields.java
  2. LoginErrorFields.java
  3. 登录表单.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;
    }

}

显然LoginFieldsLoginErrorFields 是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 (将#修改为@)