Home > database >  LazyColum only recompose when scroll
LazyColum only recompose when scroll

Time:04-22

I'm new on jetpack compose and I'm sure that I'm missing something but I don't know what?

my State model:

data class ChoiceSkillsState(
    val isLoading: Boolean = false,
    val errorWD: ErrorWD? = null,
    val skills: List<Skill> = emptyList(),
)

The Skill model:

@Parcelize
data class Skill(
    val id: Int,
    val name: String,
    val imageUrl: String? = null,
    var children: List<SkillChild>? = null,
) : Parcelable {
    @Parcelize
    data class SkillChild(
        val id: Int,
        val name: String,
        val imageUrl: String? = null,
        var note: Int? = null,
    ) : Parcelable

}

fun Skill.asChildNoted(): Boolean {
    if (!children.isNullOrEmpty()) {
        children!!.forEach {
            if (it.note != null) return true
        }
    }
    return false
}

on my viewModel

private val _state = mutableStateOf(ChoiceSkillsState())
val state: State<ChoiceSkillsState> = _state

On some event I update my skillList on my state : ChoiceSkillState. When I log, my data is updated correctly but my view is not recomposed..

There is my LazyColumn:

@Composable
private fun LazyColumnSkills(
    skills: List<Skill>,
    onClickSkill: (skill: Skill) -> Unit,
) {

    LazyColumn(
        contentPadding = PaddingValues(bottom = MaterialTheme.spacing.medium),
        verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.small),
    ) {

        items(
            items = skills,
        ) { skill ->
            ItemSkillParent(
                skill = skill,
                onClickSkill = onClickSkill
            )
        }
    }
}

Then here is my ItemSkillParent:

@Composable
fun ItemSkillParent(
    skill: Skill,
    onClickSkill: (skill: Skill) -> Unit
) {

    val backgroundColor =
        if (skill.asChildNoted()) Orange
        else OrangeLight3

    val endIconRes =
        if (skill.asChildNoted()) R.drawable.ic_apple
        else R.drawable.ic_arrow_right


    Box(
        modifier = Modifier
            .fillMaxWidth()
            .clip(shape = MaterialTheme.shapes.itemSkill)
            .background(backgroundColor)
            .clickable { onClickSkill(skill) },

        ) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(vertical = 7.dp, horizontal = 10.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Image(
                modifier = Modifier
                    .weight(1f)
                    .size(50.dp)
                    .clip(shape = MaterialTheme.shapes.itemSkillImage),

                painter = rememberAsyncImagePainter(model = skill.imageUrl),
                contentDescription = "Image skill",
                contentScale = ContentScale.Crop
            )
            Text(
                modifier = Modifier
                    .weight(6f)
                    .padding(horizontal = 10.dp),
                text = skill.name,
                style = MaterialTheme.typography.itemSkill
            )
            ButtonIconRoundedMini(
                iconRes = endIconRes,
                contentDesc = "Icon arrow right",
                onClick = { onClickSkill(skill) }
            )
        }
    }
}

My onClickSkill() will open a new Screen then pass result, then I will update my data with this :

fun updateSkill(skill: Skill) {
        val skillsUpdated = _state.value.skills
        skillsUpdated
            .filter { it.id == skill.id }
            .forEach { it.children = skill.children }

        _state.value = _state.value.copy(skills = skillsUpdated)
    }

As you can see, the background color and the iconResource should be changed, it's changing when only when I scroll. Can someone explain me what's happening there ?

CodePudding user response:

make your properties as state

 val backgroundColor by remember {
     mutableStateOf (if (skill.asChildNoted()) Orange
        else OrangeLight3)
     }
        
    val endIconRes by remember {
        mutableStateOf (if (skill.asChildNoted()) R.drawable.ic_apple else R.drawable.ic_arrow_right)
    }

CodePudding user response:

You should never use var in class properties if you want a property update to cause recomposition.

Check out Why is immutability important in functional programming?.

In this case you are updating the children property, but skillsUpdated and _state.value.skills are actually the same object - you can check the address, so Compose thinks it has not been changed.

After updating your children to val, you can use copy to update it.

val skillsUpdated = _state.value.skills.toMutableList()
for (i in skillsUpdated.indices) {
    if (skillsUpdated[i].id != skill.id) continue
    skillsUpdated[i] = skillsUpdated[i].copy(children = skill.children)
}
_state.value = _state.value.copy(skills = skillsUpdated.toImmutableList())

Note that converting a mutable list into an immutable list is also critical here, because otherwise the next time you try to update it, the list will be the same object: both toList and toMutableList return this when applied to a mutable list.

  • Related