Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

598
Vistas
Jetpack Compose – LazyColumn not recomposing

My LazyColumn is not recomposing but the value is getting updated.

If I scroll down the list and scroll back up I see correct values for the UI

MainActivity

class MainActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyTheme {
                MyApp()
            }
        }
    }
}

// Start building your app here!
@Composable
fun MyApp(vm: PuppyListViewModel =  viewModel()) {
    val puppers by vm.pups.collectAsState(emptyList())
    Surface(color = MaterialTheme.colors.background) {
        Column {
            Toolbar()
            LazyColumn {
                items(puppers) { pup ->  PuppyUI(pup, vm::seeDetails, vm::togglePuppyAdoption) }
            }
        }
    }
}

The ViewModel

class PuppyListViewModel : ViewModel() {

    val pups = PuppyRepo.getPuppies().onEach {
        println("FlowEmitted: $it")
    }

    fun togglePuppyAdoption(puppy: Puppy) = viewModelScope.launch {
        PuppyRepo.toggleAdoption(puppy.id)
    }

    fun seeDetails(puppy: Puppy) {
        println("seeDetails $puppy")
    }
}

The model


internal var IDS = 0L

data class Puppy (
    val name: String,
    val tagline: String = "",
    val race: String,
    @DrawableRes val image: Int,
    var adopted: Boolean = false,
    val id: Long = ++IDS,
)

The repository

object PuppyRepo {
    private val changeFlow = MutableStateFlow(0)
    private val pups: List<Puppy>

    private val puppyImages = listOf(
        R.drawable._1,
        R.drawable._2,
        R.drawable._3,
        R.drawable._4,
        R.drawable._5,
        R.drawable._6,
        R.drawable._7,
        R.drawable._8,
        R.drawable._9,
        R.drawable._10,
        R.drawable._11,
        R.drawable._12,
        R.drawable._13,
        R.drawable._14,
        R.drawable._15,
        R.drawable._16,
        R.drawable._17,
        R.drawable._18,
        R.drawable._19,
    )


    private val puppyNames = listOf(
        "Gordie",
        "Alice",
        "Belle",
        "Olivia",
        "Bubba",
        "Pandora",
        "Bailey",
        "Nala",
        "Rosco",
        "Butch",
        "Matilda",
        "Molly",
        "Piper",
        "Kelsey",
        "Rufus",
        "Duke",
        "Ozzy"
    )

    private val puppyTags = listOf(
        "doggo",
        "doge",
        "special dogo",
        "wrinkler",
        "corgo",
        "shoob",
        "puggo",
        "pupper",
        "small dogo",
        "big ol dogo",
        "woofer",
        "floofer",
        "yapper",
        "pupper",
        "good-boye",
        "grizlord",
        "snip-snap dogo"
    )

    private val puppyBreeds = listOf(
        "Labrador Retriever",
        "German Shepard",
        "Golden Retriever",
        "French Bulldog",
        "Bulldog",
        "Beagle",
        "Poodle",
        "Rottweiler",
        "German Shorthaired Pointer",
        "Yorkshire Terrier",
        "Boxer"
    )

    init {
        pups = puppyImages.map { image ->
            val name = puppyNames.random()
            val tagline = puppyTags.random()
            val breed = puppyBreeds.random()
            Puppy(name, tagline, breed, image)
        }
    }

    @OptIn(ExperimentalCoroutinesApi::class)
    fun getPuppies() = changeFlow.flatMapLatest { flowOf(pups) }

    fun getPuppy(puppyId: Long) = flow {
        emit(pups.find { it.id == puppyId })
    }


    suspend fun toggleAdoption(puppyId: Long): Boolean {
        val found = getPuppy(puppyId).first()?.toggleAdoption()?.let { true } ?: false
        if (found) {
            // Trigger a new emission for those that are consuming a Flow from getPuppies
            changeFlow.value = changeFlow.value + 1
        }
        return found
    }


    private fun Puppy.toggleAdoption() {
        adopted = !adopted
    }

}

The Flow pups is producing updated values as you can see in my logcat

Logcat

I've put print statements on my composables and they are not getting re-composed after the flow emits a new value.

Edit.

Lookslike Compose compares the references of the objects and since those didn't change, recomposition didn't happen even if flows were emitting new values (a bug on Compose perhaps?)

Changed the toggle functionality to re-create the instances of the elements of the list like the following and now is working.

Note: I've made Puppy.adopted a val instead of var


suspend fun toggleAdoption(puppyId: Long): Boolean {
    var found = false
    pups = pups.map {
        val isThePuppy = it.id == puppyId
        found = found || isThePuppy
        if(isThePuppy) it.copy(adopted = !it.adopted) else it.copy()
    }
    if (found) {
        // Trigger a new emission for those that are consuming a Flow from getPuppies
        changeFlow.value = changeFlow.value + 1
    }
    return found
}
over 4 years ago · Hanz Gallego
2 Respuestas
Responde la pregunta

0

The Flow pups is producing updated values as you can see in my logcat

Not exactly.

The Flow is emitting the same List of the same Puppy objects. I believe that Compose sees that the List is the same List object as before and assumes that there are no changes.

My suggested changes:

  • Make Puppy be an immutable data class (i.e., no var properties)

  • Get rid of changeFlow and have getPuppies() return a stable MutableStateFlow<List<Puppy>> (or make that just be a public property)

  • In toggleAdoption(), create a fresh list of Puppy objects and use that to update the MutableStateFlow<List<Puppy>>:

    suspend fun toggleAdoption(puppyId: Long) {
        val current = puppies.value // assumes that puppies is a MutableSharedFlow<List<Puppy>>

        val replacement = current.map { if (it.id == puppyId) it.copy(adopted = !it.adopted) else it }

        puppies.value = replacement
    }
over 4 years ago · Hanz Gallego Denunciar

0

This worked for me.

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel

class MyViewModel : ViewModel() {
    var selectables: List<Selectable> by mutableStateOf(List(100) { Selectable(name = "$it") })
        private set

    fun onTapped(tappedItem: Selectable) {
        val index = selectables.indexOf(tappedItem)
        selectables = selectables.toMutableList().also {
            it[index] = tappedItem.copy(selected = !tappedItem.selected)
        }
    }
}

data class Selectable(
    val name: String,
    var selected: Boolean = false,
)

The key parts are:

  1. Re-assigning the list rather than modifying it in-place (e.g. making selectables a MutableList and performing selectables[index] = tappedItem.copy(selected = !tappedItem.selected) wouldn't work)
  2. Re-assigning the selected item rather than modifying it in-place, e.g. the following wouldn't work
selectables = selectables.toMutableList().also {
    it[index].selected = !tappedItem.selected
}

Note that you don't have to make your data class immutable, however, making it immutable will enforce that you have to make a copy of the element in order to update it.

over 4 years ago · Hanz Gallego Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda