Consider this trivial example. Are screen1 and scr...
# compose
m
Consider this trivial example. Are screen1 and screen2 equivalent, or is screen1 less efficient because it will recompose state2 when state1 changes?
Copy code
data class State(
	val state1: State1,
	val state2: State2
)

class ViewModel{
	val state = flowOf<State?>(...)
	.distinctUntilChanged()
}

fun screen1(){
	Surface{
		val viewState by viewModel.state.collectAsState(null)

		composeFun1(viewState.state1)
		composeFun2(viewState.state2)
	}
}

fun screen2(){
	Surface{
		val viewState1 by viewModel.state.map{it.state1}.collectAsState(null)
		val viewState2 by viewModel.state.map{it.state2}.collectAsState(null)

		composeFun1(viewState1)
		composeFun2(viewState2)
	}
}
c
@Vinay Gaba Wrote an article about this. You can use
LogCompositions
from the article to test your code for recomposition counts https://kotlinlang.slack.com/archives/CJLTWPH7S/p1631121924035700
a
I don't think screen2 will work. You should use
remember(viewModel) { viewModel.state.map { it.state1 } }.collectAsState(null)
.
And yes, I think screen1 would be slightly more efficient.
m
considering i missed the remember, you mean screen2 more efficient then ?
wait. why is remember needed ?
a
? I said screen1 would be slightly more efficient.
m
but why? screen1 will recompose state2 on state1 changes. screen2 wont. can you explain please ?
a
remember
is needed because
viewModel.state.map { it.state1 }
return a new flow every time it is called, and it will be called on every recomposition.
m
ok. so assuming remember is used. why screen1 more efficient than 2 ?
Because
remember
and
collectAsState
isn't free.
m
thanks! I missed the fact that flow returns a fresh instance everytime!!!
👍 1
a
Your two examples are equivalent in terms of recomposition behavior. screen2 does slightly more work than screen1 since it manages two separate flow collectors and remembers more data in the composition.
The reason why is because the parameters to composeFun1/2 are both evaluated in the same recompose scope, so when either one changes, the calling scope of both is recomposed. We don't restart/stop composition in the middle of a function; the whole function runs again. Then you're relying on various forms of skipping to optimize.
If State1 and State2 are determined to be stable, then composeFun1/composeFun2 will skip if the parameter is equal to that of the previous run when the calling scope of both is recomposed, making both versions of the code functionally equivalent
However if the .equals implementation of those classes has to traverse a deep tree of objects to check equality to determine skipping, this can sometimes be on the same order of expense as just recomposing
m
yeah, makes sense
thanks!
👍 1