I'm trying to understand recomposition and large s...
# compose
t
I'm trying to understand recomposition and large state object and it seems that if I collect a StateFlow with :
Copy code
val state by viewModel.state.collectAsState()
Then all the child of that component are recomposed if any child use any property of the state. What would be a proper way to collect a large state but only update sub component on some part of the state. For example the state contains a media item and a progress state. when only the progress change I only want to recompose the progress bar and not all components that display the media information.
z
Without seeing your code it’s impossible to say exactly, but if
state
is not a stable type, or its properties aren’t stable, that could be the issue.
t
I tested with the state marked as stable and all it's val as stable type too (it's a simple data class). The code is also very simple.
Copy code
Row {
                    LogCompositions("CompactPlayer")
                    val isPlaying by viewModel.isPlayingState.collectAsState()
                    PlayPauseIcon(isPlaying) {
                        viewModel.playPause()
                    }
                    StopIcon {
                        viewModel.stop()
                    }
                    NextIcon {
                        viewModel.next()
                    }
                }
When isPlayingState change the Row is recomposed as well as all icons. If I pass the viewmodel to the PlayPauseIcon and collectasstate there then only the PlayPauseIcon is recomposed
In that case I've break it down to a simple
Copy code
val isPlayingState: StateFlow<Boolean>
and it still occurs 😞
z
right, that makes sense because the recompose scope in which
isPlaying
is read is the body of the
Row
lambda, so when
isPlaying
is changed then that lambda will be re-executed
t
Thanks I've read those but I must admit there's something I do not understand. Let's say you have a stateflow with an object that contains 3 fields what is the proper way to collect it and have 3 reusable components that don't know anything about the root object but only each different fields?
👍 1
z
I should have clarified this initially, but there’s nothing inherently wrong with the code you posted. It’s fine that the Row’s lambda is re-executed, that’s how it’s supposed to work.
t
The thing is that because of it it became very hard to diagnose bad code during learning phase and too much re composition :( If I split each state then wrap the components with a simple wrapper function then it behave as expected. I really want to ensure that those large state updated very often like having the progress does not impact perfs.
z
You really shouldn’t have to worry too much about recomposition counts as long as you’re writing code that’s reasonably well-organized, unless you’ve got an infinite recomposition loop or done benchmarking and profiling and have clear data showing that recomposition is actually causing issues.
The only place where I think it maybe pays to think about this a little harder is for animations, but even then simple animations generally work great even if you’re technically restarting a little more than you need to.
Focus on writing readable code, the compose compiler will generally do the right thing and is always getting better.
t
While I love the theory and like to believe I do publish Android apps since 10 years with tons of low end devices. Understanding how the things I use works to not have to refactor all to workaround issues is quite important too :( It's really harder with compose to be sure the choices are good due to global lack of experience and fast moving project.
Are you sure collectasstate on large state frequently updated that will be then used in many sub components in multiple complex screens is a proper approach and I should not split the state in smaller ones before reaching the "view" system ?
z
I think that’s a hard question to answer in the abstract, but if you have a state that large it sounds like it might be cleaner architecturally to break it up.
t
Well it's not that large but a standard player state, so playing active, current item (title, subtitle, image, ...) , progress .... Main issue being the progress that is updated often. Breaking the progress out just for the sake of performance is IMO wrong. So it's either keep the "player" state or break it up to the smallest part. But that's a huge difference in code and in number of collect and coroutines
z
How huge? One option would be to represent player state as a mutable object where each property is backed by a
State
so you don’t have to collect each one and can just read it directly. You could do that directly in your view model, or just do it in your composable with something like this:
Copy code
val playerState by viewModel.state.collectAsState()
val playingActive by remember { derivedStateOf { playerState.active } }
val title by remember { derivedStateOf { playerState.title } }
val subtitle by remember { derivedStateOf { playerState.subtitle } }
val progress by remember { derivedStateOf { playerState.progress } }
But for progress, depending on how you’re rendering it, it might make for a smoother UI to actually just read the progress value on every frame so it moves smoothly instead of jumping every few milliseconds. You could do that with
awaitFrameMillis
in a
LaunchedEffect
.
t
Thanks lots of reading and tests for the week end. The progress is pushed to the state so value is same as pooling. This state is actually for many different players with different refresh rates. Pooling from UI does not bring advantage. Later when working on details I'll try to animate the slide position change. But so many things to figure out before that. But so far love Compose I even find fun in rebuilding old stuff ')
162 Views