首页 > 解决方案 > 强制数据绑定以获取所有中间媒体 livedata / mediatorlivedata 更改

问题描述

我的问题是我的数据绑定值仅从我的 MediatorLiveData 获取最新更新passwortStatus并跳过所有中间值。这会导致奇怪的视图问题,因为并非所有视图都正确更新。

这是一个例子:

当我在编辑文本中键入内容时,我passwordCalculator会执行并查看是否满足某些特定条件。例如,当我写“1Ab”(数字、BigLetter、SmallLetter)时,passwordStatus livedata值输出:PasswordStatus.HasDigit、PasswordStatus.HasLowerCase、PasswordStatus.HasUpperCase。

现在的问题是:passwordStatus在我的片段中观察这个给了我所有上述值(PasswordStatus.HasDigit、PasswordStatus.HasLowerCase、PasswordStatus.HasUpperCase),我只在我的数据绑定变量中得到 PasswortStatus.HasUpperCase。

片段观察

viewModel.passwordCalculator.passwordStatus.observe(viewLifecycleOwner) {
    Timber.d("STATE CHANGED TO FRAGMENT $it")
}

/// OUTPUT
"STATE CHANGED TO FRAGMENT PasswordStatus.HasDigit"
"STATE CHANGED TO FRAGMENT PasswordStatus.HasLowerCase"
"STATE CHANGED TO FRAGMENT PasswordStatus.HasUpperCase"

布局内的数据绑定观察

<include
    android:id="@+id/has_minimum_letters_indicator"
    layout="@layout/registration_indicator"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="8dp"
    app:applied="@{PasswortStateConverter.INSTANCE.convertHasMinimunCharakterToBoolean(viewModel.passwordCalculator.passwordStatus)}"
    app:layout_constraintStart_toStartOf="@+id/registration_title"
    app:layout_constraintTop_toBottomOf="@+id/registration_title"
    app:tText="@{@string/registration_password_policy_minimum_amount}" />

数据绑定状态调试

object PasswortStateConverter {
    fun convertHasMinimunCharakterToBoolean(state: PasswordStatus): Boolean {
        Timber.d("STATE CHANGED TO $state")
        return state is PasswordStatus.HasMinCharackter
    }

    "STATE CHANGED TO PasswortStatus.NONE" (Default Value instead of PasswortStatus.HasDigit)
    "STATE CHANGED TO PasswortStatus.NONE" (Default Value instead of PasswortStatus.HasLowerCase)
"STATE CHANGED TO FRAGMENT PasswordStatus.HasUpperCase" (Getting latest value)

密码计算器

class PasswordCalculator @Inject constructor() : TextWatcher {

    private companion object {
        private val lowerCasePattern = Pattern.compile("[a-z]")
        private val upperCasePattern = Pattern.compile("[A-Z]")
        private val digitPattern = Pattern.compile("[0-9]")
        private val specialPattern = Pattern.compile("[!@#$%^&*()_=+{}/.<>|\\[\\]~-]")
        private const val NOT_ENOUGH_LETTERS = 4
        private const val MIN_LETTERS = 8
        private const val BEST_AMOUNT_LETTERS = 12
    }

    private val hasUpperCase = MutableLiveData(false)
    private val hasLowerCase = MutableLiveData(false)
    private val hasDigit = MutableLiveData(false)
    private val hasSpecialCharackter = MutableLiveData(false)
    private val hasMoreThanCharacters = MutableLiveData(false)

    private val _passwordStatus: MediatorLiveData<PasswordStatus> = MediatorLiveData()
    val passwordStatus: LiveData<PasswordStatus> get() = _passwordStatus

    private val _passwordStrength: MediatorLiveData<PasswordStrength> = MediatorLiveData()
    val  passwortStrength: LiveData<PasswordStrength> get() = _passwordStrength

    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
    override fun afterTextChanged(s: Editable?) {}

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        if (s != null) {
            hasUpperCase.value = s.hasUpperCase()
            hasLowerCase.value = s.hasLowerCase()
            hasDigit.value = s.hasDigit()
            hasSpecialCharackter.value = s.hasSpecialCharackter()
            hasMoreThanCharacters.value = s.length >= MIN_LETTERS
            calculateStrength(s)
        }
    }

    fun isPasswordValid(): Boolean {
        return hasUpperCase.value == true &&
                hasLowerCase.value == true &&
                hasDigit.value == true &&
                hasSpecialCharackter.value == true &&
                hasMoreThanCharacters.value == true
    }

    private fun CharSequence.hasLowerCase(): Boolean = lowerCasePattern.matcher(this).find()
    private fun CharSequence.hasUpperCase(): Boolean = upperCasePattern.matcher(this).find()
    private fun CharSequence.hasDigit(): Boolean = digitPattern.matcher(this).find()
    private fun CharSequence.hasSpecialCharackter(): Boolean = specialPattern.matcher(this).find()

    private fun postStatus(
        bool: Boolean,
        positiveStatus: PasswordStatus,
        negativeStatus: PasswordStatus,
    ) {
        when (bool) {
            true -> _passwordStatus.value = positiveStatus
            false -> _passwordStatus.value = negativeStatus
        }
    }

    private fun calculateStrength(charSequence: CharSequence) {
        when {
            charSequence.length in NOT_ENOUGH_LETTERS until MIN_LETTERS ->
                _passwordStrength.value = PasswordStrength.BAD
            charSequence.length < MIN_LETTERS ->
                _passwordStrength.value = PasswordStrength.NONE
            charSequence.length >= BEST_AMOUNT_LETTERS && charSequence.hasSpecialCharackter() ->
                _passwordStrength.value = PasswordStrength.HIGH
            charSequence.length >= MIN_LETTERS ->
                _passwordStrength.value = PasswordStrength.GOOD
        }
    }

    init {
        _passwordStatus.addSource(hasUpperCase) { postStatus(it, PasswordStatus.HasUpperCase, PasswordStatus.HasNoUpperCase) }
        _passwordStatus.addSource(hasLowerCase) { postStatus(it, PasswordStatus.HasLowerCase, PasswordStatus.HasNoLowerCase) }
        _passwordStatus.addSource(hasDigit) { postStatus(it, PasswordStatus.HasDigit, PasswordStatus.HasNoDigit) }
        _passwordStatus.addSource(hasSpecialCharackter) { postStatus(it, PasswordStatus.HasSpecialCharackter, PasswordStatus.HasNoSpecialCharakter) }
        _passwordStatus.addSource(hasMoreThanCharacters) { postStatus(it, PasswordStatus.HasMinCharackter, PasswordStatus.HasNotMinCharackter) }
    }
}

所以我的最后一个问题是:如何强制数据绑定获取所有中间值(例如 PasswordStatus.HasDigit、PasswordStatus.HasLowerCase、PasswordStatus.HasUpperCase),而不仅仅是最新值(PasswordStatus.HasUpperCase)?

标签: androidkotlinandroid-databindingandroid-binding-adapter

解决方案


推荐阅读