When I want to define a local composable to be reu...
# compose
s
When I want to define a local composable to be reused multiple times, does it matter if it’s defined as a val or a fun? To explain with code my example1 and example2 cases:
Copy code
@Composable fun foo() {
  @Composable fun example1() {}
  val example2: @Composable () -> Unit = {}

  if(..) {example1()} else {example2(); example2()}
}
If it’s a
val
, will it have to do any extra work on each recomposition as if I was doing
val x = someComputation
in composition? Maybe anything that the compiler plugin does better in one way? In general I guess my question is, is there any special reason why I would want to go with one over the other? Or if I really should just make it a private top level composable instead?
z
Looking at the bytecode can often help answer questions like this
l
Will looking at JVM bytecode also help with understanding the behavior on K/JS and K/N, or should we expect the compiler to have some substantial differences for the different platforms? Essentially, does the Compose Compiler map as close as possible to 1:1 for the different Kotlin compilers?
z
The compose compiler operates on ir, which is cross-platform
s
In a file named
ComposableTest.kt
with no package:
@Composable fun example1() {}
is
GETSTATIC ComposableTestKt$asd$1.INSTANCE : LComposableTestKt$asd$1;
---
val example2: @Composable () -> Unit = {}
is two lines
Copy code
GETSTATIC ComposableTestKt$asd$example2$1.INSTANCE : LComposableTestKt$asd$example2$1;

CHECKCAST kotlin/jvm/functions/Function0
then calling them
example1()
is
INVOKEVIRTUAL ComposableTestKt$asd$1.invoke ()V
---
example2()
is
INVOKEINTERFACE kotlin/jvm/functions/Function0.invoke ()Ljava/lang/Object; (itf)
And for a normal top level private function with signature:
@Composable private fun example3() {}
Calling it is:
INVOKESTATIC ComposableTestKt.example3 ()V
So there are some differences, not to say I can really interpret what those are, this is the first time I ever really look at bytecode tbh 😄 One is using
INVOKEVIRTUAL
, one
INVOKEINTERFACE
the other
INVOKESTATIC
, not sure if this does after all have any implications on how the compiler interprets stable-ness and so on. Might be interesting to try and run the compose compiler reports on these, might do that sometime to see if that tells me anything.
e
()V
indicates that the compose compiler isn't actually operating, which is a known issue with using "Show bytecode" https://youtrack.jetbrains.com/issue/KTIJ-23396
but I don't expect that to fundamentally change how the calls are made
z
I’m not 100% sure, but I think that means example1 has its own type, and it’s invoking the method directly on the concrete type, whereas example 2 has to be cast to a generic interface and then the interface method is invoked. Not sure exactly what the practical implications are, probably negligible in any case. I think interface dispatches are slightly slower in super hot code paths.
s
Hey, getting back to this. Somehow, not sure exactly how this happens, when I had the
Copy code
val dismissButton = @Composable {
  TextButton(onClick = { dismiss() }) {
    Text(stringResource(android.R.string.cancel))
  }
}
syntax, I got a crash with stacktrace
java.lang.IllegalStateException: Compose Runtime internal error. Unexpected or incorrect use of the Compose internal runtime API (Unexpected anchor value, expected a negative anchor). Please report to Google or use <https://goo.gle/compose-feedback>
In fact very similar to this issue https://issuetracker.google.com/issues/204897513 Making a sample project I could not for the life of me reproduce it. In my project there’s some shenanigans going on with showing an
androidx.compose.material3.AlertDialog
and the text is defined higher up and passed in there in various places depending on a boolean conditional. Anyway, switched to the local
@Composable fun
syntax and it no longer crashes now. Can’t really report this without having a repro though. Should I report with the stacktrace besides the fact that I can’t actually reproduce it and I can’t share the code where this bug does in fact happen?
Stack trace for reference