https://kotlinlang.org logo
#compose-android
Title
# compose-android
h

Håkon Pettersen

10/11/2023, 12:37 PM
Hi, I need some help understanding why my composable undergoes unnecessary recompositions and how to fix it (details in the thread 🧵). Whenever a value in the state is updated,
ListOfItemInCard
in
GenericDetailSettingRows
is recomposed. I checked the hash codes of each
GenericDetailRow
, and they remained the same through every recomposition. Any help would be much appreciated.
Parent composable:
Copy code
@Composable
fun GenericDetailScreen(
    settingRows: List<GenericDetailRow.Settings>,
    onRowSelected: (GenericDetailRow) -> Unit,
) {
    Scaffold { paddingValues ->
        Column(
            modifier = Modifier
                .padding(paddingValues)
                .padding(horizontal = dimensionResource(id = R.dimen.content_padding))
                .verticalScroll(rememberScrollState()),
            verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.half_content_padding)),
        ) {
            GenericDetailSettingRows(
                settingRows = settingRows,
                onRowSelected = onRowSelected,
            )
        }
    }
}
Child-composable:
Copy code
@Composable
fun GenericDetailSettingRows(
    settingRows: List<GenericDetailRow.Settings>,
    onRowSelected: (GenericDetailRow.Settings) -> Unit,
) {
    if (settingRows.isNotEmpty()) {
        HeaderText(text = { it.getString(R.string.settings) })
        ListOfItemInCard(
            items = settingRows,
            withDividers = true,
        ) { row ->
            when (row) {
                GenericDetailRow.Settings.AlarmSettings -> {
                    RowImageTextArrow(
                        leadingIconRes = R.drawable.ic_alarm_24dp,
                        text = { it.getString(R.string.generic_detail_page_smart_alarm_settings) },
                        onClick = { onRowSelected(row) },
                    )
                }
                ...
            }
        }
    }
}
Content set from fragment:
Copy code
return ComposeView(requireContext()).apply {
    setContent {
        val state = viewModel.state.collectAsStateWithLifecycle(initialValue = GenericDetailState())

        EvaTheme {
            GenericDetailScreen(
                settingRows = state.value.settingRows,
                onRowSelected = viewModel::onRowSelected,
            )
        }
    }
}
View state (A lot of the state is unused attempting to solve this "bug"):
Copy code
data class GenericDetailState(
    val name: String = "",
    val icon: String = "",
    val settingRows: List<GenericDetailRow.Settings> = emptyList(),
    ...
)
GenericDetailRow:
Copy code
sealed interface GenericDetailRow {
    // Used to ensure consistent ordering of rows in the view.
    val priority: Int

    sealed class Settings : GenericDetailRow {

        object GenericSettings : Settings() {
            override val priority = 1
        }
    ...
Lib-version:
Copy code
compose = "2023.06.00"
compose-compiler = "1.5.1"
It seems to be related to the
onRowSelected: (GenericDetailRow.Settings) -> Unit
for some reason. If I replace
RowImageTextArrow
with a standard Button and leave the onClick-lambda empty, it skips recomposition.
z

Zach Klippenstein (he/him) [MOD]

10/11/2023, 7:47 PM
If it’s not recomposing on every frame continuously, I wouldn’t spend a ton of time trying to track a couple extra recompositions. There just isn’t much payoff, and that number could change between versions.
h

Håkon Pettersen

10/12/2023, 6:48 AM
Good point @Zach Klippenstein (he/him) [MOD]. I was bothered by not knowing what was triggering the recompositions. After investigating, I discovered it was due to unstable lambdas. To address this, I created a stable interface which is now utilized by the composables
Copy code
@Stable
fun interface RowClickListener {
    fun selected()
}
z

Zach Klippenstein (he/him) [MOD]

10/12/2023, 1:49 PM
Is your view model class marked as stable? If so, does passing
{ viewModel.onRowSelected() }
instead of a method reference, would that make the lambda stable?