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
I've put print statements on my composables and they are not getting re-composed after the flow emits a new value.
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
}
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With