Examples to show how to pass data to composable functions using function parameters(i.e. pass by value), CompositionLocal and static CompositionLocal
There are a few ways you can pass data to a composable function:
Pass by Value (function parameter)
CompositionLocal
Static CompositionLocal
Pass by Value is a conventional way. CompositionLocal and static CompositionLocal is a Jetpack Compose way, but static CompositionLocal is useless in my opinion (will be explained later).
Pass by Value
This is a very simple example to pass counter
value to the Parent()
composable function, then increments it by 1 and passes it to the Child()
composable function. Finally, it calls the GrandChild()
composable function without any parameters.
Let's investigate the code, what do you think the Logcat outputs are during the first composition and the subsequent recomposition?
private val tag = "CompLocal"
@Composable
fun PassByValueDemo() {
var counter by remember {
mutableStateOf(-1)
}
MyButton(onClick = { ++counter }, text = "PassByValue Demo")
if(counter < 0) return
Log.d(tag, "************** Pass by Value **************")
Parent(counter)
}
@Composable
private fun Parent(value: Int) {
Log.d(tag, "Start Parent - value: $value")
Child(value + 1)
Log.d(tag, "End Parent - value: $value")
}
@Composable
private fun Child(value: Int) {
Log.d(tag, "Start Child - value: $value")
GrandChild()
Log.d(tag, "End Child - value: $value")
}
@Composable
private fun GrandChild() {
Log.d(tag, "Start GrandChild")
Log.d(tag, "End GrandChild")
}
Logcat Output - First Composition (first clicked)
value
is incremented by 1 and passed into the Child()
composable
************** Pass by Value **************
Start Parent - value: 0
Start Child - value: 1
Start GrandChild
End GrandChild
End Child - value: 1
End Parent - value: 0
Logcat Output - Recomposition (second clicked)
A very important thing to notice is the GrandChild()
composable is skipped.
************** Pass by Value **************
Start Parent - value: 1
Start Child - value: 2
End Child - value: 2
End Parent - value: 1
CompositionLocal
To accomplish the exact behavior, CompositionLocal
can be used.
Here are the simple steps:
-
Create a
CompositionLocal
variable (usingcompositionLocalOf()
that is accessible from the compostable functions that you want to use it.
private val LocalInt = compositionLocalOf { 0 }
-
Provide value to the
CompositionLocal
(i.e.LocalInt
) usingCompositionLocalProvider
.
CompositionLocalProvider( LocalInt provides 0, ) { //call your composable function here }
* `LocalInt provides 0` is similar to `LocalInit.provides(0)`. It is a [Kotlin infix notation](https://vtsen.hashnode.dev/kotlin-infix-notation-is-confusing).
-
Access the
CompositionLocal
's value byCompositionLocal.current
.
LocalInt.current
The full code looks like this
private val LocalInt = compositionLocalOf { 0 }
private val tag = "CompLocal"
@Composable
fun CompositionLocalDemo() {
var counter by remember {
mutableStateOf(-1)
}
MyButton(onClick = { ++counter }, text = "CompositionLocal Demo")
if(counter < 0) return
Log.d(tag, "************** Using CompositionLocal **************")
CompositionLocalProvider(
LocalInt provides counter,
) {
Parent()
}
}
@Composable
private fun Parent() {
Log.d(tag, "Start Parent - LocalInt: ${LocalInt.current} ")
CompositionLocalProvider(
LocalInt provides LocalInt.current + 1,
) {
Child()
}
Log.d(tag, "End Parent - LocalInt: ${LocalInt.current}")
}
@Composable
private fun Child() {
Log.d(tag, "Start Child - LocalInt: ${LocalInt.current} ")
GrandChild()
Log.d(tag, "Emd Child - LocalInt: ${LocalInt.current} ")
}
@Composable
private fun GrandChild() {
Log.d(tag, "Start GrandChild")
Log.d(tag, "End GrandChild")
}
This has the same outputs as the Pass by Value example above.
Logcat Output - First Composition (first clicked)
************** Pass by Value **************
Start Parent - value: 0
Start Child - value: 1
Start GrandChild
End GrandChild
End Child - value: 1
End Parent - value: 0
Logcat Output - Recomposition (second clicked)
************** Pass by Value **************
Start Parent - value: 1
Start Child - value: 2
End Child - value: 2
End Parent - value: 1
Static CompositionLocal
You can replace CompositionLocal
with static CompositionLocal
. This code
private val LocalInt = compositionLocalOf { 0 }
is replaced by
private val LocalInt = staticCompositionLocalOf { 0 }
and everything remains the same.
However, the outputs are NOT the same as Pass by Value and CompositionLocal
. Changes to the CompositionLocal
's value triggers the entire composition tree to be recomposed.
Logcat Output - First Composition (first clicked)
************** Pass by Value **************
Start Parent - value: 0
Start Child - value: 1
Start GrandChild
End GrandChild
End Child - value: 1
End Parent - value: 0
Logcat Output - Recomposition (second clicked)
************** Pass by Value **************
Start Parent - value: 1
Start Child - value: 2
Start GrandChild
End GrandChild
End Child - value: 2
End Parent - value: 1
As you can see, GrandChild()
composable function is called/recomposed even though it doesn't access the LocalInt.current
value. This is a complete waste of unnecessary recompositions in my opinion.
The official document states that you should only use staticCompositionLocalOf()
for a value that doesn't change. But the issue is, how do you prevent the user or any developer from changing it? You can't.
Therefore, it seems to be we should just use CompositionLocalOf()
and NOT use staticCompositionLocalOf()
as a best practice.
The official document does mention about performance benefits of using
staticCompositionLocalOf
if the value is not changed, but how much benefits exactly?I agree to use
staticCompositionLocalOf()
only if it is a constant value and can't be changed. Then, this prevents the users from misusing it. What do you think?
Conclusion
CompositionLocal
is just a Jetpack Compose way as a replacement of passing by value to a composable function. This may be helpful if you have a global variable that is often being used by your composable functions.
Static CompsitionLocal
triggers the entire composable tree to be recomposed if its value is changed. So, use it carefully. My recommendation is, don't use it.
Source Code
GitHub Repository: Demo_UnderstandComposeConcept
Originally published at https://vtsen.hashnode.dev.
Top comments (0)