1

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 ?

MakiX
  • 326
  • 3
  • 8
  • 1
    when you scroll it's recycled items and when item got redrawed it takes new data and shows correct state to redraw the part runtime (without scrolling) you should make the property as state or live data can you show Skill object? – Narek Hayrapetyan Apr 21 '22 at 09:59
  • I add the skill object. – MakiX Apr 21 '22 at 10:17
  • When you say : "you should make the property as state or live data" I don't understand, cause my state is already typed as State. About witch property are you thinking ? – MakiX Apr 21 '22 at 10:19
  • I'm about Skill, or pass Skill As state not exactly object Or you should make your list as mutableStateListOf () Skills – Narek Hayrapetyan Apr 21 '22 at 10:30
  • I also try to put a custom variable : val skillState = mutableStateListOf() then update it like : skillState.clear() skillState.addAll(skillsUpdated) – MakiX Apr 21 '22 at 10:43
  • I don't see any code that should update your data, `onClickSkill` method? Please add it. – Phil Dukhov Apr 21 '22 at 11:08
  • @PylypDukhov I add this informations – MakiX Apr 21 '22 at 13:16

2 Answers2

1

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.

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
  • Thank you ! Works for me and make me understand what was happening – MakiX Apr 22 '22 at 09:44
  • 1
    @MakiX You're welcome. If this solved your question, please accept it using checkmark under the votes counter and upvote (if you didn't yet) – Phil Dukhov Apr 22 '22 at 10:37
0

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)
    }
Narek Hayrapetyan
  • 1,731
  • 15
  • 27