This android tutorial has the below code snippet:
@Composable
private fun MyApp() {
Surface(color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
My first thought was that the Surface composable gets its Greeting child by calling the lambda parameter containing the child and getting it back as a return value. But later in the tutorial we get this example:
@Composable
private fun Greeting(name: String) {
Surface(color = MaterialTheme.colors.primary) {
Column(modifier = Modifier.padding(24.dp)) {
Text(text = "Hello,")
Text(text = name)
}
}
}
Somehow the Column is aware of both the "Hello" Text and the name Text, even though calling the lambda would only give the name Text as a return value.
So my question is: what is the mechanism that makes a Composable aware of its children?
CodePudding user response:
Similarly to for example suspend
functions, @Composable
functions are processed by the compiler in a very special way. This is to allow automatic recompositions and also to implicitly pass the context between components.
Documentation for @Composable specifies:
A useful mental model for Composable functions is that an implicit "composable context" is passed into a Composable function, and is done so implicitly when it is called from within another Composable function. This "context" can be used to store information from previous executions of the function that happened at the same logical point of the tree.
We can see it in action by writing a simple composable function and analyzing the resulting bytecode. Taking this source code:
@Composable
fun Foo() {
Text("foo")
}
We get the bytecode consisting of hundreds of instructions and some of the resulting code is even placed in a separate, synthesized class. We will focus on most important parts:
public static final void Foo(androidx.compose.runtime.Composer, int);
As we can see, our parameterless Foo()
function actually implicitly receives a Composer
and some integer.
3: invokeinterface #24, 2 // InterfaceMethod androidx/compose/runtime/Composer.startRestartGroup:(I)Landroidx/compose/runtime/Composer;
Another Composer
object is created by calling startRestartGroup()
on the received Composer
.
55: invokestatic #73 // Method androidx/compose/material/TextKt."Text-fLXpl1I":(Ljava/lang/String;Landroidx/compose/ui/Modifier;JJLandroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontFamily;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/text/style/TextAlign;JIZILkotlin/jvm/functions/Function1;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/runtime/Composer;III)V
This is a call to Text()
. It is hard to read due to the large number of optional parameters, but we can notice that it also receives a Composer
object. This parameter is synthesized, we can't find it in the list of Text()
parameters - similarly as in our Foo()
function.
Foo()
passes the composer created with startRestartGroup()
to Text()
.
While I don't know the exact functionality and the meaning of the Composer
, we can clearly see that Compose framework implicitly passes the context between composable functions, making possible to wire components together.