Composable lambda list during recomposition in Android
Composable lambda list during recomposition in Android ź“ė Ø
This post is to see how to render a composable list, more at an educational level than a practical one, since in most cases we can use lazy lists.
I once had to deal with a screen that painted several nested lists dynamically, using generic types, quite complex due to the way it was built and also using theĀ hybrid XML + Jetpack Compose system, so in the end I ended up using a composable list, or at least that was the solution I found by reusing the pieces I had available.
But again, in most cases this approach will be the exception or you will probably come up with other solutions.
Composables list
In this example, the UI will receive three values āāthat will be emitted every second:
private val viewTypeState: MutableState<ViewType?> = mutableStateOf(null)
init {
emitViewTypes()
}
private fun emitViewTypes() {
lifecycleScope.launch {
delay(1\_000)
viewTypeState.value = ViewType.Header
delay(1\_000)
viewTypeState.value = ViewType.Body
delay(1\_000)
viewTypeState.value = ViewType.Footer
}
}
The list will be dynamically created in the UI and displayed like this:
@Composable
private fun RenderViews() {
val views: MutableList<@Composable () -> Unit> = remember {
mutableListOf()
}
viewTypeState.value?.let { viewType ->
val view = getView(text = viewType.title)
views.add(view)
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(16.dp),
) {
views.forEach { view ->
view()
}
}
}
}
@Composable
private fun getView(text: String): @Composable () -> Unit {
return {
Text(text = text)
}
}
That is, theĀ getView()
Ā method returns a composable lambda that, when invoked, will display a text view.
And theĀ RenderViews()
Ā method goes through the list of views, invoking them item by item, to display the views on the screen.
When running the above code, it looks like the result will be this:
Compose list real
So whatās going on?
Reference in memory of lambdas
What happens is that every time the lambda is invoked,Ā the same reference is obtained in memory, so:
- The Header is emitted, the view with the header is added to the list, and the Header view is displayed.
- The Body is emitted, the view with the body is added to the list, and the Body view is displayed twice, since the first item that had the value of Header now has the value of Body.
- The Footer is emitted, the view with the Footer is added to the list, and the Footer view is displayed three times, since the first item and the second item that had the value of Body now have the value of Footer.
And so on, the last value emitted is added to the list and affects the previous elements, since the list lambdas have the same reference, so they will alwaysĀ render the same viewĀ (Text)Ā with a different valueĀ (title):
Different contexts to have different lambda references
Therefore, a solution could have different contexts to generate different instances. In this case, using the when block:
@Composable
private fun RenderViews() {
val views: MutableList<@Composable () -> Unit> = remember {
mutableListOf()
}
when (val viewType = viewTypeState.value) {
ViewType.Body -> {
val view = getView(text = viewType.title)
views.add(view)
}
ViewType.Footer -> {
val view = getView(text = viewType.title)
views.add(view)
}
ViewType.Header -> {
val view = getView(text = viewType.title)
views.add(view)
}
null -> Unit
}
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(16.dp),
) {
views.forEach { view ->
view()
}
}
}
I hope you donāt have to do this kind of workaround in your projects, but if you do, at least this can help you face future challenges. š
Info
This article is previously published on proandroiddev.com (proandroiddev
)