首页 > 解决方案 > 数据绑定和:输入字段验证和操作;活动/片段导航

问题描述

我目前正在学习数据绑定以及随之而来的所有新事物。目前,我在如何正确实施事物方面遇到了很多困难,因此寻求帮助。

在我的应用程序的这个特定部分,我们将讨论我遇到的最多问题:登录/注册表格;

过去,我的代码很简单:

在保持简单的同时,每个人都很开心。

此刻,我试图转移到数据绑定,虽然我已经设法了解了很多,但有些事情我不太确定。

当前代码和以下具体问题:

登录视图模型:

class SignInViewModel(application: Application) : AndroidViewModel(application) {

    private val context = getApplication<Application>().applicationContext
    val signInForm = SignInForm()

    private val _signInState = MutableLiveData<SignInState>()
    val signInState: LiveData<SignInState>
        get() = _signInState

    fun userSignIn() {
        _signInState.value = SignInState.Loading

        Firebase.auth.signInWithEmailAndPassword(signInForm.email!!.value!!, signInForm.password!!.value!!)
            .addOnCompleteListener {
                _signInState.value =
                    if (it.isSuccessful)
                        SignInState.SignedIn
                    else
                        SignInState.Error(it.exception!!.localizedMessage!!)
            }
    }


    // E-Mail
    val emailValidationResponse = MediatorLiveData<String?>().apply {
        addSource(signInForm.email as LiveData<String>) {
            value = emailValidation()
        }
    }

    val passwordValidationResponse = MediatorLiveData<String?>().apply {
        addSource(signInForm.password as LiveData<String>) {
            value = passwordValidation()
        }
    }

    private fun emailValidation(): String? {
        return when {
            signInForm.email?.value.isNullOrEmpty() -> {
                context.getString(R.string.error_message_field_is_empty)
            }
            !Patterns.EMAIL_ADDRESS.matcher(signInForm.email?.value!!).matches() -> {
                context.getString(R.string.error_message_invalid_email)
            }
            else -> null
        }
    }

    private fun passwordValidation(): String? {
        return when {
            signInForm.password?.value.isNullOrEmpty() -> {
                context.getString(R.string.error_message_field_is_empty)
            }
            signInForm.password?.value!!.length < 8 -> {
                context.getString(R.string.error_message_password_is_too_short, USER_PASSWORD_MIN_CHARACTERS)
            }
            else -> null
        }
    }

SignInForm.kt

data class SignInForm(
    override val email: MutableLiveData<String>? = MutableLiveData(),
    val password: MutableLiveData<String>? = MutableLiveData()
) : Form()

登录状态.kt

sealed class SignInState {
    object Loading : SignInState()
    object SignedIn : SignInState()
    data class Error(val errorMessage: String) : SignInState()
}

登录片段

class SignInFragment : Fragment() {

    private val signInViewModel: SignInViewModel by viewModels()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val binding = FragmentSignInBinding.inflate(inflater, container, false)
        binding.lifecycleOwner = viewLifecycleOwner
        binding.viewModel = signInViewModel

        signInViewModel.signInState.observe(viewLifecycleOwner, { state ->
            when (state) {
                is SignInState.SignedIn -> {
                    proceedToProfileScreen(requireActivity())
                }
            }
        })

        binding.signInInputEditTextEmail.setOnFocusChangeListener { _, hasFocus -> if (!hasFocus) hideKeyboard() }
        binding.signInInputEditTextPassword.setOnFocusChangeListener { _, hasFocus -> if (!hasFocus) hideKeyboard() }

        binding.signInButtonGoToSignUp.setOnClickListener {
            this.findNavController().navigate(R.id.action_signInFragment_to_signUpFragment)
        }

        binding.signInButtonGoToForgotPassword.setOnClickListener {
            this.findNavController().navigate(R.id.action_signInFragment_to_forgotPasswordFragment)
        }

        return binding.root
    }
}

fragment_sign_in.xml

<layout> ....

<com.google.android.material.textfield.TextInputLayout
                    android:id="@+id/sign_in_input_layout_email"
                    style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
                    setError="@{viewModel.emailValidationResponse}"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/input_hint_email"
                    app:errorEnabled="true">

                    <com.google.android.material.textfield.TextInputEditText
                        android:id="@+id/sign_in_input_edit_text_email"
                        android:layout_width="match_parent"
                        android:layout_height="55dp"
                        android:inputType="text"
                        android:text="@={viewModel.signInForm.email}" />

                </com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
                    android:id="@+id/sign_in_input_layout_password"
                    style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
                    setError="@{viewModel.passwordValidationResponse}"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/input_hint_password"
                    app:endIconMode="password_toggle"
                    app:errorEnabled="true">

                    <com.google.android.material.textfield.TextInputEditText
                        android:id="@+id/sign_in_input_edit_text_password"
                        android:layout_width="match_parent"
                        android:layout_height="55dp"
                        android:inputType="textPassword"
                        android:text="@={viewModel.signInForm.password}" />

                </com.google.android.material.textfield.TextInputLayout>


<androidx.appcompat.widget.AppCompatButton
                    android:id="@+id/sign_in_button_submit"
                    style="@style/Widget.MaterialComponents.Button.OutlinedButton"
                    android:layout_width="match_parent"
                    android:layout_height="55dp"
                    android:onClick="@{() -> viewModel.userSignIn()}"
                    android:text="@string/sign_in_button_submit"
                    android:textColor="@android:color/black" />

<androidx.appcompat.widget.AppCompatButton
                    android:id="@+id/sign_in_button_go_to_sign_up"
                    style="@style/Widget.MaterialComponents.Button.TextButton"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/sign_in_button_go_to_sign_up"
                    android:textColor="@android:color/black"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent" />

                <androidx.appcompat.widget.AppCompatButton
                    android:id="@+id/sign_in_button_go_to_forgot_password"
                    style="@style/Widget.MaterialComponents.Button.TextButton"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/sign_in_button_go_to_forgot_password"
                    android:textColor="@android:color/black"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintTop_toTopOf="parent" />
... </layout>

问题:

标签: androidkotlinmvvmandroid-databindingandroid-jetpack

解决方案


这是我最终使用的:

[1] 输入表单验证 + [3] 启用/禁用按钮。

SignInForm.kt

data class SignInForm(
    val email: MutableLiveData<String> = MutableLiveData(),
    val password: MutableLiveData<String> = MutableLiveData()
) {

    val emailError = MediatorLiveData<String?>().apply {
        value = ""
        addSource(email) {
            value = validateEmail(it)
        }
    }

    val passwordError = MediatorLiveData<String?>().apply {
        value = ""
        addSource(password) {
            value = validatePassword(it)
        }
    }
}

InputValidators.kt

fun validateEmail(email: String?): String? {
    return when {
        email.isNullOrEmpty() -> TheContext.applicationContext().getString(R.string.error_message_field_is_empty)
        !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches() -> TheContext.applicationContext().getString(R.string.error_message_invalid_email)
        else -> null
    }
}

fun validatePassword(password: String?): String? {
    return when {
        password.isNullOrEmpty() -> TheContext.applicationContext().getString(R.string.error_message_field_is_empty)
        password.length < 8 -> TheContext.applicationContext().getString(R.string.error_message_password_is_too_short, USER_PASSWORD_MIN_CHARACTERS)
        else -> null
    }
}

fragment_sign_in.xml

<layout> ...

<data>
        <variable
            name="viewModel"
            type="my.test.movieexpert.loginscreen.viewmodel.SignInViewModel" />
</data>



... <com.google.android.material.textfield.TextInputLayout
                    android:id="@+id/sign_in_input_layout_email"
                    style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
                    inputValidation="@{viewModel.signInForm.emailError}"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/input_hint_email"
                    app:errorEnabled="true"> ...



... <com.google.android.material.textfield.TextInputLayout
                    android:id="@+id/sign_in_input_layout_password"
                    style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
                    inputValidation="@{viewModel.signInForm.passwordError}"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/input_hint_password"
                    app:endIconMode="password_toggle"
                    app:errorEnabled="true"
                    app:helperText=""> ...

... <androidx.appcompat.widget.AppCompatButton
                    android:id="@+id/sign_in_button_submit"
                    emailError="@{viewModel.signInForm.emailError}"
                    passwordError="@{viewModel.signInForm.passwordError}"
                    setButtonState="@{viewModel.signInState}"
                    android:layout_width="match_parent"
                    android:layout_height="55dp"
                    android:background="@color/colorPrimaryDark"
                    android:onClick="@{() -> viewModel.userSignIn()}"
                    android:text="@string/sign_in_button_submit"
                    android:textColor="@android:color/white"
                    android:textSize="17sp" /> ...

... </layout>

[2] 导航保持不变:

登录片段

binding.signInButtonGoToSignUp.setOnClickListener {
            this.findNavController().navigate(R.id.action_signInFragment_to_signUpFragment)
        }

推荐阅读