【问题标题】:MutableStateFlow is not emitting values after 1st emit kotlin coroutineMutableStateFlow 在第一次发出 kotlin 协程后没有发出值
【发布时间】:2020-10-01 13:00:56
【问题描述】:

这是我的 FirebaseOTPVerificationOperation 类,其中定义了我的 MutableStateFlow 属性并更改了值,

    @ExperimentalCoroutinesApi
class FirebaseOTPVerificationOperation @Inject constructor(
    private val activity: Activity,
    val logger: Logger
) {
    private val _phoneAuthComplete = MutableStateFlow<PhoneAuthCredential?>(null)
    val phoneAuthComplete: StateFlow<PhoneAuthCredential?>
        get() = _phoneAuthComplete

    private val _phoneVerificationFailed = MutableStateFlow<String>("")
    val phoneVerificationFailed: StateFlow<String>
        get() = _phoneVerificationFailed

    private val _phoneCodeSent = MutableStateFlow<Boolean?>(null)
    val phoneCodeSent: StateFlow<Boolean?>
        get() = _phoneCodeSent

    private val _phoneVerificationSuccess = MutableStateFlow<Boolean?>(null)
    val phoneVerificationSuccess: StateFlow<Boolean?>
        get() = _phoneVerificationSuccess

    fun resendPhoneVerificationCode(phoneNumber: String) {
        _phoneVerificationFailed.value = "ERROR_RESEND"
    }
}

这是我的viewmodal,我从中监听状态流属性的变化,如下,

class OTPVerificationViewModal @AssistedInject constructor(
    private val coroutinesDispatcherProvider: AppCoroutineDispatchers,
    private val firebasePhoneVerificationListener: FirebaseOTPVerificationOperation,
    @Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    @AssistedInject.Factory
    interface Factory {
        fun create(savedStateHandle: SavedStateHandle): OTPVerificationViewModal
    }

    val phoneAuthComplete = viewModelScope.launch {
        firebasePhoneVerificationListener.phoneAuthComplete.filter {
            Log.e("1","filter auth $it")
            it.isNotNull()
        }.collect {
            Log.e("2","complete auth $it")
        }
    }

    val phoneVerificationFailed = viewModelScope.launch {
        firebasePhoneVerificationListener.phoneVerificationFailed.filter {
            Log.e("3","filter failed $it")
            it.isNotEmpty()
        }.collect {
            Log.e("4","collect failed $it")
        }
    }

    val phoneCodeSent = viewModelScope.launch {
        firebasePhoneVerificationListener.phoneCodeSent.filter {
            Log.e("5","filter code $it")
            it.isNotNull()
        }.collect {
            Log.e("6","collect code $it")
        }
    }

    val phoneVerificationSuccess = viewModelScope.launch {
        firebasePhoneVerificationListener.phoneVerificationSuccess.filter {
            Log.e("7","filter success $it")
            it.isNotNull()
        }.collect {
            Log.e("8","collect success $it")
        }
    }

    init {
        resendVerificationCode()
        secondCall()
    }

    private fun secondCall() {
        viewModelScope.launch(coroutinesDispatcherProvider.io) {
            delay(10000)
            resendVerificationCode()
        }
    }

    fun resendVerificationCode() {
        viewModelScope.launch(coroutinesDispatcherProvider.io) {
            firebasePhoneVerificationListener.resendPhoneVerificationCode(
                getNumber()
            )
        }
    }

    private fun getNumber() =
            "+9191111116055"
}

问题是 firebasePhoneVerificationListener.phoneVerificationFailed 在 vi​​ewmodal 中被触发以进行第一次调用, init { resendVerificationCode() }

但是对于init { secondCall() } 的第二次调用, firebasePhoneVerificationListener.phoneVerificationFailed 不会在 viewmodal 中触发,我不知道为什么会发生,任何原因或解释都会非常适用。

电流输出: filter auth null filter failed filter code null filter success null filter failed ERROR_RESEND collect failed ERROR_RESEND

预期输出: filter auth null filter failed filter code null filter success null filter failed ERROR_RESEND collect failed ERROR_RESEND filter failed ERROR_RESEND collect failed ERROR_RESEND

【问题讨论】:

  • 尝试使用不同的值更新_phoneVerificationFailed。当您设置一个等于当前值的StateFlow 时,不会通知其观察者。这就像使用 distinctUntilChanged 运算符。

标签: kotlin kotlin-coroutines kotlin-coroutine-channel


【解决方案1】:

使用Channel:这确实会在两次发送相同的值后发出。

  1. 将此添加到您的 ViewModel

    val _intent = Channel(Channel.CONFLATED)

  2. 使用send / trySend 输入值

    _intent.send(intentLocal)

  3. 观察流

    _intent.consumeAsFlow().collect { //做点什么 }

【讨论】:

  • ChannelMutableStateFlow 好?
  • MutableStateFlow 本质上是混为一谈的,所以你应该使用Channel.CONFLATED 来模仿它的行为
【解决方案2】:

Pankaj 的回答是正确的,StateFlow 不会两次发出相同的值。正如documentation 建议的那样:

状态流中的值使用Any.equals 比较以类似于distinctUntilChanged 运算符的方式进行合并。它用于将传入的更新合并到 MutableStateFlow 中的 value,并在新值等于先前发出的值时抑制向收集器发出值。

因此,要解决此问题,您可以创建一个包装类并覆盖equals(和hashCode)方法以返回false,即使这些类实际上是相同的:

sealed class VerificationError {
    object Resend: VerificationError()

    override fun equals(other: Any?): Boolean {
        return false
    }

    override fun hashCode(): Int {
        return Random.nextInt()
    }
}

【讨论】:

  • 这本身并不能解决问题,但它帮助我排除了状态变化是否被流看到 - 它不是
【解决方案3】:

状态流发出的值是混合的,不会两次发出相同的连续结果,您可以认为好像条件检查正在验证旧发出的值不等于新发出的值。

电流输出: 过滤验证空 过滤失败 过滤代码空 过滤成功 null 过滤失败 ERROR_RESEND 收集失败ERROR_RESEND

(过滤失败ERROR_RESEND collect failed ERROR_RESEND) 这与发出的旧值相同,因此您不会看到它们被发出。

【讨论】:

    【解决方案4】:

    StateFlow 是 SharedFlow:https://github.com/Kotlin/kotlinx.coroutines/issues/2034


    val shared = MutableSharedFlow(
        replay = 1,
        onBufferOverflow = BufferOverflow.DROP_OLDEST,
    )
    shared.tryEmit(value)
    

    PS。 Vel_daN:爱​​你所做的事情?。

    【讨论】:

      【解决方案5】:

      我想我对这个问题有了更深入的了解。首先要确定的是,对于StateFlow,不建议使用可变集合类型(如MutableList等)。因为 MutableList 不是线程安全的。如果核心代码中有多个引用,可能会导致程序崩溃。

      以前,我使用的方法是包装类并覆盖equals方法。但是,我认为这种解决方案不是最安全的方法。最安全的方法是深拷贝,Kotlin 提供的 toMutableList() 和 toList() 方法都是深拷贝。 emit方法判断是否有变化,取决于equals()的结果是否相等。

      我有这个问题的原因是使用emit()的数据类型是:SparseArray。 StateFlow 为 SparseArray 调用 equals 方法。当 MutableList 发生变化时,此时 equals 的结果并没有变化(即使 MutableList 的 equals 和 hashcode 方法发生变化)。

      最后,我将类型更改为 SparseArray。虽然增加和删除数据会造成性能损失,但这也从根本上解决了问题。

      【讨论】:

      • 非常感谢,调用 kotlin toList() 和 toMutableList() 完成了这项工作。你拯救了我的一天。
      【解决方案6】:

      如上所述,LiveData 每次都会发出数据,而 StateFlow 只会发出不同的值。 tryEmit() 不起作用。就我而言,我找到了两种解决方案。

      1. 如果你有 String 数据,你可以这样再次发出:

        private fun emitNewValue() {
            subscriber.value += " "
            subscriber.value.dropLast(1)
        }
        

        对于另一个类,您可以使用它(或创建扩展函数):

         private fun <T> emitNewValue(value: T) {
             if (subscriber.value == value) {
                 subscriber.value = null
             }
             subscriber.value = value
         }
        

      但这是一种糟糕且有问题的方式(另外会发出 两次 值)。

      1. 尝试查找所有更改其值的订阅者。可以不明显。例如,焦点更改侦听器Switch(复选框)。当您切换Switch 时,文本也可以更改,因此您应该订阅此侦听器。以同样的方式,当您聚焦其他视图时,错误文本可能会发生变化。

      【讨论】:

        【解决方案7】:

        合并流后我遇到了类似的问题。 如果使用 == 判断相等,则不会执行 emit() 函数。

        解决问题的方法:可以包裹一层,重写hashCode()和equals()方法。 equals() 方法直接返回 false。 该解决方案适用于我的代码。合并后的流也发生了变化。

        Pankaj 的回答是正确的,StateFlow 不会两次发出相同的值。

        在换行之前,即使内容不同,==的结果依然为真。

        【讨论】:

          猜你喜欢
          • 2017-12-17
          • 1970-01-01
          • 2018-09-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-11-27
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多