Why snapshotFlow is trigged with MutableState as property delegate?

118 Views Asked by At

I have a situation with the snapshotFlow recently when trying to observe changes from a MutableState. Here is the snippet

val searchQuery = mutableStateOf("")
snapshotFlow { searchQuery }.collectLatest {
               
}

But when the searchQuery changed, there was nothing happened in collectLatest block. It seems the snapshotFlow not find any changes from searchQuery. I guessed. Then I changed the searchQuery declare to var searchQuery by mutableStateOf(""). The snapshotFlow works as expected. Can anyone explain me why? Really appreciate!

2

There are 2 best solutions below

3
On BEST ANSWER

Please refer to this StackOverflow answer:

SnapshotFlow works similarly to recomposition - it tracks reads of State.value to know when to emit. So for snapshotFlow to work, the value has to be read inside of the snapshotFlow {} block, which is not happening

With your current approach, the snapshotFlow doesn't know which changes to observe. When using the by keyword, searchQuery.value is implicitly called, and thus the snaphotFlow can detect the changes.

To solve this, please try

val searchQuery = mutableStateOf("")
snapshotFlow { searchQuery.value }.collectLatest {
    // ...      
}

or use the by keyword.

As the official documentation remains vague about how the approaches for declaring a MutableState differ, this Reddit post summarizes the differences well.

2
On

snapshotFlow creates a Flow from observable Snapshot state. (e.g. state holders returned by mutableStateOf.)

If the result of block is not equal to the previous result, the flow will emit that new result. (This behavior is similar to that of Flow.distinctUntilChanged.)

https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotFlow.kt;drc=e17d836265f395ae589ba254c4efddd29a314ae3;l=75

With val searchQuery = mutableStateOf("") you are querying against instance of MutableState, which needs a new MutableState of MutableState to be collected.

The example below is only for demonstration purposes, unless explicitly required you don't need to store a State inside a State. We are collecting state holderof MutableState of query. If you assign new MutableState with button you will see that snapshotFlow collects new instance of MutableState

@Preview
@Composable
private fun SnapshowFlowTest() {

    var query = remember {
        mutableStateOf(mutableStateOf(""))
    }

    LaunchedEffect(Unit) {
        snapshotFlow {
            query.value
        }
            .collect {
                println("State: $it, value: ${it.value}")
            }
    }

    Column {
        TextField(
            value = query.value.value,
            onValueChange = {
                query.value.value = it
            }
        )

        Button(
            onClick = {
                query.value = mutableStateOf("Default Value")
            }
        ) {
            Text("Set new MutableState")
        }
    }
}

With var searchQuery by mutableStateOf("") you are collecting the change in value of MutableState which is String.