Composition can be described as how nodes of composition tree are structured. They don't have to be necessarily be ui Composables either and they don't have to be drawn on screen to be part of composition.
Your example is correct any conditional statement can change how it will be structured on recomposition.
Composition is first time a Composable takes place in composition tree, when any State is read any part of scope of that Composable or whole Composable is updated with new value, based on some other factors it might be updated with same value as well such as SnapshotMutationPolicy or stability of Composable, this is recomposition. When some logic removes a Composable from the tree that Composable leaves composition.
DisposableEffect function is a non ui Composable that you can track when a Composable enters or exits composition.
In example below Text and DisposableEffect enters recomposition when counter is 3, stays in composition and Text is recomposed with new counter value until 6 and when it reaches 6 DisposableEffect and Text leaves composition
@Preview @Composable private fun UIComposableSample() { val context = LocalContext.current var counter by remember { mutableStateOf(0) } if (counter in 3..5) { Text("Counter: $counter") DisposableEffect(Unit){ Toast.makeText(context, "enter recomposition $counter", Toast.LENGTH_SHORT).show() onDispose { Toast.makeText(context, "exit recomposition $counter", Toast.LENGTH_SHORT).show() } } } Button(onClick = { counter++ }) { Text("Counter: $counter") } }
And there is no need to be ui composable for enter, recomposition, leave composition lifecycle either.
@Composable private fun NonUIComposableSample() { val context = LocalContext.current var counter by remember { mutableStateOf(0) } var color by remember { mutableStateOf(Color.Red) } if (counter in 3..5) { DisposableEffect(Unit) { Toast.makeText(context, "Entering Composition counter: $counter", Toast.LENGTH_SHORT).show() color = Color.Yellow onDispose { color = Color.Green Toast.makeText(context, "Exiting Composition counter: $counter", Toast.LENGTH_SHORT).show() } } } Button(onClick = { counter++ }) { Text("Counter: $counter", color = color) } }
or this one LaunchedEffect enter composition when counter is bigger than 0 and multiply of 3 LaunchedEffect enters composition and Snackbar is shown if you press Button and counter increases Snackbar stops is removed otherwise it waits until it's time out to fade out and leave composition.
/** * Adding this LaunchedEffect to composition when condition is true * Same goes for if this was remember(LaunchedEffect under the hood uses `remember(key){}` * when condition is met remember gets added to composition and it gets removed when it's not met */ @Composable private fun LaunchedEffectExample(scaffoldState: ScaffoldState) { var counter by remember { mutableStateOf(0) } if (counter > 0 && counter % 3 == 0) { // `LaunchedEffect` will cancel and re-launch if // `scaffoldState.snackbarHostState` changes LaunchedEffect(scaffoldState.snackbarHostState) { // Show snackbar using a coroutine, when the coroutine is cancelled the // snackbar will automatically dismiss. This coroutine will cancel whenever // if statement is false, and only start when statement is true // (due to the above if-check), or if `scaffoldState.snackbarHostState` changes. scaffoldState.snackbarHostState.showSnackbar("LaunchedEffect snackbar") } } // This button increase counter that will trigger LaunchedEffect OutlinedButton( modifier = Modifier .fillMaxWidth() .padding(vertical = 8.dp), onClick = { counter++ } ) { Text("LaunchedEffect Counter $counter") } }
And for Ui Composable there are three phases alongside with composition which is invoked in depth three trevesal.
Compose has three main phases:
Composition: What UI to show. Compose runs composable functions and creates a description of your UI.
Layout: Where to place UI. This phase consists of two steps: measurement and placement. Layout elements measure and place themselves and any child elements in 2D coordinates, for each node in the layout tree.
Drawing: How it renders. UI elements draw into a Canvas, usually a device screen.
Even if a Composasble has 0.dp size or not drawn on screen(if you assign Modifier.drawWithContent{} not draw content), or even if it's not or placed( if you don't call place in placement scope) it still might be part of composition tree and means that it's composed.
Let's say you have ui tree like the one below
Column() { println("Parent Scope") Column() { println("Child1 Outer Scope") Text("Child1 Outer Content") Column() { println("Child1 Inner Scope") Text("Child1 Inner Content") } } Column() { println("Child2 Scope") Text("Child2 Content") } }
Composition creates nodes such as like the one below
Parent Layout / \ / \ / \ / \ Child1 Outer Child2 | Child1 Inner
Prints
I Parent Scope I Child1 Outer Scope I Child1 Inner Scope I Child2 Scope
This is the order of Composition happening starts tree from left goes until inner child, goes to next branch and so on. Then in Layout phase child1 Inner gets measured first, Child1 Outer, Child2 then Parent gets measured with info from child and they get place same order Composition happens.
If the Composables are like this
Parent Composable / | \ / | \ / | \ / | \ / | \ C1 Outer C2Outer C3outer / | \ / / \ \ / / \ \ / / \ \ C1 Middle C2 InnerA C2 InnerB C3 Inner | C1 Inner Prints: I Paren Scope I Child1 Outer Scope I Child1 Middle Scope I Child1 Inner Scope I Child2 Outer Scope I Child2 InnerA Scope I Child2 InnerB Scope I Child3 Outer Scope I Child3 Inner Scope
This is how they are composed in composition. On recomposition based on which composable reads a value only it or one its parent or entire composition tree might get recomposed.
https://stackoverflow.com/a/73181512/5457853
https://stackoverflow.com/a/73181512/5457853
https://stackoverflow.com/a/77007725/5457853
LazyColumn()might have some nodes that are off-screen but ready to be used when the user starts scrolling. But, if we assume in this case that all the composables fromLoginScreen()up to the root do not do this, then enter/leave will generally align with "included in the UI"/"removed from the UI".