Kacper
08/30/2023, 2:09 PMdonut-hole skipping
one written by Vinay Gaba?ascii
08/30/2023, 2:16 PMascii
08/30/2023, 2:18 PMKacper
08/30/2023, 2:33 PMvide
08/30/2023, 3:08 PMAlbert Chang
08/30/2023, 3:12 PMHow does recomposition really work?This "simple" question is the hardest to answer because there are so many things under the hood. Looks like the main part you want to ask about is recompose scope? The "donut-hole skipping" one and the one by Zach are both good posts on recompose scope. If you have read them, it might be better to ask more specifically on the part you didn't understand.
Kacper
09/01/2023, 7:11 AMKacper
09/01/2023, 7:18 AMvide
09/01/2023, 9:29 AM@Composable
fun Component(counter1: Int) = Column {
LogCompositions("Component")
Text(counter1.toString())
CustomBlock { LogCompositions("CustomBlockScope1") }
}
@Composable
fun CustomBlock(content: @Composable () -> Unit) {
LogCompositions("CustomBlock")
content()
}
Output when counter1
changes:
Component
CustomBlockScope1
Why doesn't it skip recomposition of the composable lambda passed to CustomBlock
?vide
09/01/2023, 9:40 AMvide
09/01/2023, 9:54 AMvide
09/01/2023, 9:59 AMLogCompositions
function?Kacper
09/01/2023, 10:03 AMclass Ref(var value: Int)
// Note the inline function below which ensures that this function is essentially
// copied at the call site to ensure that its logging only recompositions from the
// original call site.
@Composable
inline fun LogCompositions(tag: String, msg: String) {
if (BuildConfig.DEBUG) {
val ref = remember { Ref(0) }
SideEffect { ref.value++ }
Log.d(tag, "Compositions: $msg ${ref.value}")
}
}
vide
09/01/2023, 10:03 AMvide
09/01/2023, 10:04 AMvide
09/01/2023, 10:04 AMvide
09/01/2023, 10:04 AMKacper
09/01/2023, 10:05 AMvide
09/01/2023, 10:06 AMLogCompositions
function is defined outside MainActivity
but it doesn't skip if it's defined inside. 🤔Kacper
09/01/2023, 10:06 AMascii
09/01/2023, 10:07 AMvide
09/01/2023, 10:09 AMinline fun LogCompositions(
stable msg: String
stable tag: String? = @dynamic LiveLiterals$MainActivityKt.String$param-tag$fun-LogCompositions$class-MainActivity()
unused stable <this>: MainActivity
)
ascii
09/01/2023, 10:10 AMvide
09/01/2023, 10:10 AMvide
09/01/2023, 10:11 AMascii
09/01/2023, 10:13 AMvide
09/01/2023, 10:19 AMascii
09/01/2023, 10:20 AMvide
09/01/2023, 10:56 AMvide
09/01/2023, 10:56 AMvide
09/01/2023, 10:58 AM@Composable fun CustomBlock(content: @Composable () -> Unit) {
Log.v("RECOMPOSITION", "CustomBlock body")
content()
}
@Composable fun Test1(paramNeverChanges: Int = 0, paramChangesRapidly: Int) {
Log.v("RECOMPOSITION", "Test1-paramChangesRapidly=$paramChangesRapidly")
CustomBlock {
Log.v("RECOMPOSITION", "Test1-CustomBlock scope")
paramNeverChanges // reading the param triggers a recomposition of the scope
}
}
@Composable fun Test2(paramNeverChanges: Int = 0, paramChangesRapidly: Int) {
Log.v("RECOMPOSITION", "Test2-paramChangesRapidly=$paramChangesRapidly")
CustomBlock {
Log.v("RECOMPOSITION", "Test2-CustomBlock scope")
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var paramChangesRapidly by mutableIntStateOf(0)
lifecycleScope.launch { while (true) { paramChangesRapidly += 1; delay(1.seconds) } }
setContent {
Test1(paramChangesRapidly = paramChangesRapidly)
Test2(paramChangesRapidly = paramChangesRapidly)
}
}
}
this will log:
Test1-paramChangesRapidly=2
Test1-CustomBlock scope
Test2-paramChangesRapidly=2
Test1-paramChangesRapidly=3
Test1-CustomBlock scope
Test2-paramChangesRapidly=3
Test1-paramChangesRapidly=4
Test1-CustomBlock scope
Test2-paramChangesRapidly=4
etc.vide
09/01/2023, 11:02 AMvide
09/01/2023, 11:13 AMvide
09/01/2023, 12:21 PMvide
09/01/2023, 1:03 PMAlbert Chang
09/01/2023, 2:00 PMAlbert Chang
09/01/2023, 2:06 PMvide
09/01/2023, 4:09 PM