22

这是我的 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"
    }
}

这是我的视图模式,我从中监听状态流属性的变化,如下所示,

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 

在第一次调用时在 viewmodal 中触发,

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
4

8 回答 8

14

Pankaj 的答案是正确的,StateFlow不会两次发出相同的值。正如文档所示:

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

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

sealed class VerificationError {
    object Resend: VerificationError()

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

    override fun hashCode(): Int {
        return Random.nextInt()
    }
}
于 2021-03-22T08:56:19.007 回答
11

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

  1. 将此添加到您的 ViewModel

    val _intent = Channel(Channel.CONFLATED)

  2. send使用/放置值trySend

    _intent.send(intentLocal)

  3. 观察流

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

于 2020-06-14T07:09:27.447 回答
11

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

当前输出: filter auth null filter failed filter code null filter success null filter failed ERROR_RESEND collect failed ERROR_RESEND

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

于 2020-11-10T03:15:33.993 回答
5

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:爱​​你所做的。

于 2021-10-21T06:09:45.197 回答
2

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

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

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

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

于 2021-04-09T12:32:04.653 回答
1

如上所述,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时,文本也可以更改,因此您应该订阅此侦听器。以同样的方式,当您关注其他视图时,错误文本可能会发生变化。
于 2021-09-20T12:48:29.510 回答
0

您可以将 _phoneVerificationFailed 设为 null 并在两个呼叫之间发送 null!

于 2022-02-14T11:07:06.643 回答
0

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

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

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

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

于 2021-03-25T06:49:35.573 回答