Search code examples
androidandroid-jetpack-compose

Why is the collected StateFlow value from ViewModel not updated synchronously?


Composable code:

val testNumber by viewModel.testNumber.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
    viewModel.changeTestNumberValue()
    Log.d("TestNumber", "Test number in Composable = $testNumber")
}

ViewModel code:

private val _testNumber = MutableStateFlow(0)
val testNumber = _testNumber.asStateFlow()

fun changeTestNumberValue() {
    _testNumber.value = 1
    Log.d("TestNumber", "Test number in ViewModel = ${_testNumber.value}")
}

I expected that these two logs (in viewmodel and composable) would both be logged with Test number = 1. But this is the actual value printed:

Test number in ViewModel = 1
Test number in Composable = 0

I know that the value of testNumber will eventually be updated, but I don't understand why this is not synchronous. Or that's how StateFlow works (collect asynchronously) ??


Solution

  • This is because testNumber is already read during composition and it was 0, you will need to directly tell the compose to read most recent value, thus adding another one LaunchedEffect will make it work the way you expect.

    val testNumber by createFeedbackVM.testNumber.collectAsStateWithLifecycle()
    LaunchedEffect(Unit) {
        createFeedbackVM.changeTestNumberValue()
        Log.d("TestNumber", "Test number in Composable = $testNumber")//prints previous value
    }
    LaunchedEffect(testNumber) {
        Log.d("TestNumber", "Test new number in Composable = $testNumber") //prints new value
    }
    

    You can also use delay() to see that value is updated, but not synchronously because this is how compose works and how it is different from plain-old android View framework.

    LaunchedEffect(Unit) {
            createFeedbackVM.changeTestNumberValue()
            delay(100)
            Log.d("TestNumber", "Test number in Composable = $testNumber")//prints 1
        }