Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MutableStateFlow is not emitting values after 1st emit kotlin coroutine

This is my FirebaseOTPVerificationOperation class, where my MutableStateFlow properties are defined, and values are changed,

    @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"
    }
}

This is my viewmodal, from where i am listening the changes in stateflow properties, as follows,

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"
}

The issue is that

firebasePhoneVerificationListener.phoneVerificationFailed 

is fired in viewmodal for first call of,

init {
   resendVerificationCode()
}

but for second call of:

init {
   secondCall()
}

firebasePhoneVerificationListener.phoneVerificationFailed is not fired in viewmodal, I don't know why it happened, any reason or explanation will be very appericated.

Current Output:

filter auth null
filter failed 
filter code null
filter success null
filter failed ERROR_RESEND
collect failed ERROR_RESEND

Expected Output:

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
like image 700
Reprator Avatar asked Jun 11 '20 19:06

Reprator


People also ask

How do you observe a MutableStateFlow?

collect() is the right way of observing MutableStateFlow for changes. You just need to allocate some coroutine to make this observing. And that means that yes, it will wait at collect() until you no longer need to observe it, so you then cancel it.

How do I update my mutable StateFlow?

A mutable state flow is created using MutableStateFlow(value) constructor function with the initial value. The value of mutable state flow can be updated by setting its value property. Updates to the value are always conflated. So a slow collector skips fast updates, but always collects the most recently emitted value.

Is StateFlow hot or cold?

StateFlow is a hot flow—it remains in memory as long as the flow is collected or while any other references to it exist from a garbage collection root. You can turn cold flows hot by using the shareIn operator.

Is StateFlow stable?

Today we're pleased to announce the release of version 1.4. 0 of the Kotlin Coroutines library. The highlights of the release are StateFlow and SharedFlow, which are being promoted to stable API.


1 Answers

Pankaj's answer is correct, StateFlow won't emit the same value twice. As the documentation suggests:

Values in state flow are conflated using Any.equals comparison in a similar way to distinctUntilChanged operator. It is used to conflate incoming updates to value in MutableStateFlow and to suppress emission of the values to collectors when new value is equal to the previously emitted one.

Therefore, to resolve this issue you can create a wrapping class and override the equals (and hashCode) method to return false even if the classes are in fact the same:

sealed class VerificationError {
    object Resend: VerificationError()

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

    override fun hashCode(): Int {
        return Random.nextInt()
    }
}
like image 176
Sir Codesalot Avatar answered Sep 17 '22 10:09

Sir Codesalot