Home > other >  Why does trySend emit fake data?
Why does trySend emit fake data?

Time:12-07

I need to get user auth state in MVVM. In the repository I do this:

override fun getAuthResponse() = callbackFlow  {
    val listener = AuthStateListener {
        Log.d(TAG, "currentUser: "   (currentUser == null)) //Line2
        trySend(it.currentUser == null)
    }
    auth.addAuthStateListener(listener)
    awaitClose {
        auth.removeAuthStateListener(listener)
    }
}

"Line2" will always print true because the user is not authenticated. Then in ViewModel I have:

fun getAuthResponse() = repo.getAuthResponse()

And inside activity:

setContent {
    //...
    val response = viewModel.getAuthResponse().collectAsState(initial = false).value
    Log.d(TAG, "response: $response") //Line1
}

Since setContent is a composable function, when I open the app, it fires twice. That means that the log statement at "Line1", is triggered twice. When it first fires, I get:

response: false
currentUser: true

So, the response is printed before, even if I called getAuthResponse() at a previous line. The problem is that for some kind of reason even if the current user is null, I got printed in the activity that the user is not null. When it fires the second time, I got the correct data:

response: true
currentUser: true

Why do I get a non-null user object? Does trySend emit fake data?

CodePudding user response:

The problem here is that flows are by design asynchronous. Even if you expect AuthStateListener to be invoked immediately after registering it, still the flow initially don't have any value. collectAsState() requires a value, it can't be just empty and for this reason it requires you to provide an initial value. You provided false and this is what you initially get as the response. Then almost immediately listener is invoked, it emits true and then return changes to true as well.

There are multiple ways how we could solve this kind of problems. For example, we could initially show a loading screen until we get the correct value. In the above case, as we are able to get the value for currentUser directly, without using a listener, I suggest using StateFlow instead. StateFlow is different than a regular Flow as it is able to keep its "current" value. The easiest would be to use MutableStateFlow:

override fun getAuthResponse(): StateFlow<Boolean> {
    val flow = MutableStateFlow(auth.currentUser == null)

    val listener = AuthStateListener {
        Log.d(TAG, "currentUser: "   (currentUser == null)) //Line2
        flow.value = it.currentUser == null
    }
    auth.addAuthStateListener(listener)

    return flow
}

setContent {
    //...
    val response = viewModel.getAuthResponse().collectAsState().value
    Log.d(TAG, "response: $response") //Line1
}

This solution may be not 100% correct as I couldn't test it, but I hope you get the idea.

  • Related