https://kotlinlang.org logo
#compose
Title
# compose
h

Hamba

11/25/2023, 3:56 AM
i have a mutablestate value in my viewmodel and i want to run a function every time it changes. my initial idea is to just make an invisible Composable that relies on that value and use launchedeffect on to run the function but that feels hacky. whats the proper way to do something without having to insert a ui element like that? im guessing something with observables, but how do i combine it with a variable that is currently set as a mutablestatelistof()
z

Zach Klippenstein (he/him) [MOD]

11/25/2023, 4:20 AM
snapshotFlow
h

Hamba

11/25/2023, 4:35 AM
im struggling to understand how to use this. the variable of note is
segments
and i want to run
history.addSnapshot(segments)
every time
segments
is changed, so i can keep a list of the history of
segments
Copy code
class TimelineModel {
    var segments = mutableStateListOf<TimelineSegment>()
    var history = TimelineHistory()
so for the snapshotflow, i tried this but it doesnt trigger when segments has an element added/removed
Copy code
LaunchedEffect(viewModel.timelineModel.segments) {
        snapshotFlow {
            viewModel.timelineModel.history.addSnapshot(viewModel.timelineModel.segments)
        }
    }
i think im misunderstanding what it does, is it meant to run the code in its block? or do i have to add stuff after snapshotFlow to get it to run each time a change is made
tried this but also doesnt work
Copy code
LaunchedEffect(viewModel.timelineModel.segments) {
        snapshotFlow { viewModel.timelineModel.segments }
            .collect { new ->
                viewModel.timelineModel.history.addSnapshot(new)
                println(viewModel.timelineModel.history)
            }
    }
Copy code
LaunchedEffect(viewModel.timelineModel.segments.toList()) {
    viewModel.timelineModel.history.addSnapshot(viewModel.timelineModel.segments)
}
This ends up working, but it still feels wrong to put something like this in the UI at all. its not UI related, and i feel there should be a way to make this update occur just within the viewmodel
its just using the UI as a crutch to handle the automatic running when a change is detected
a

andriyo

11/25/2023, 5:41 AM
i'd argue that it should not be a mutablestate in the first place if it's not supposed to be consumed by compose UI, but instead it should be a SharedFlow or StateFlow. They are kinda similar to Compose' mutableState (i think stateflow and mutablestate were developed by different teams hence overlap in functionality but they are the same essentially). The flows (shared or state) are more powerful though if you need to do things reactively like you want, anyway, if you stil want to use mutablestate as source, you can use snapshotFlow without LaunchEffect in Compose but stay in ViewModel
Copy code
class TimelineViewModel : ViewModel() {
    val timelineModel = TimelineModel()

    init {
        // Observe changes in segments
        viewModelScope.launch {
            snapshotFlow { timelineModel.segments }
                .collect { currentSegments ->
                    // Update history whenever segments change
                    timelineModel.history.addRecord(currentSegments)
                }
        }
    }
}
☝🏻 1
h

Hamba

11/25/2023, 6:05 AM
well the segments are mutable and the ui displays based on whats in the segments. but the history isnt directly consumed by the ui. which is why i think the update of the history when the segments change shouldnt occur inside the UI. but then if i say revert the segments to some past state stored in the history, that will be reflected in the ui changing
but that snippet works for what i wanted, thanks
8 Views