I am experimenting with Android's Jetpack Compose.
For simple use cases everything is working as expected,
but I am having some trouble with missing recompositions for a more advanced case.
My Model:
I am simulating a Storage system for ingredients, where
- an ingredient consists of a name and an optional icon:
data class Ingredient(val name: String, @DrawableRes val iconResource: Int? = null)
- a StorageItem consists of an ingredient and a stock (amount of this ingredient in storage):
data class StorageItem(val ingredient: Ingredient, var stock: Int)
My Composables:
My composables for the StorageUi are supposed to list all storage items
and display icon and name for the ingredient, as well as the stock.
For this post, I stripped it of all irrelevant modifiers and formatting to simplify readability.
(Note that I overloaded my StorageScreen composable with a second version without view model
for easier testing and in order to facilitate the Preview functionality in Android Studio.)
@Composable
fun StorageScreen(viewModel: StorageViewModel) {
StorageScreen(
navController = navController,
storageItems = viewModel.storageItems,
onIngredientPurchased = viewModel::purchaseIngredient
)
}
@Composable
fun StorageScreen(storageItems: List<StorageItem>, onIngredientPurchased: (StorageItem) -> Unit) {
Column {
TitleBar(...)
IngredientsList(storageItems, onIngredientPurchased)
}
}
@Composable
private fun IngredientsList(storageItems: List<StorageItem>, onIngredientPurchased: (StorageItem) -> Unit) {
LazyColumn {
items(storageItems) { storageItem ->
IngredientCard(storageItem, onIngredientPurchased)
}
}
}
@Composable
private fun IngredientCard(storageItem: StorageItem, onIngredientPurchased: (StorageItem) -> Unit) {
Card(
Modifier.clickable { onIngredientPurchased(storageItem) }
) {
Row {
ImageIcon(...)
Text(storageItem.ingredient.name)
Text("${storageItem.stock}x")
}
}
}
My View Model:
In my ViewModel, I
- create a mutable state list (initialization with data not shown here)
- provide the event handler that increases the stock, if the user taps an ingredient card
class StorageViewModel : ViewModel() {
var storageItems = mutableStateListOf<StorageItem>()
private set
fun purchaseIngredient(storageItem: StorageItem) {
storageItem.stock += 1
}
}
The Problem: No recomposition takes place when changing the stock of an ingredient
I tried changing the event handler to simply remove the tapped item from the list:
fun purchaseIngredient(storageItem: StorageItem) {
storageItems.remove(storageItem)
}
And voilà, the UI recomposes and the tapped ingredient is gone.
What I learned:
- mutableStateListOf() does observe changes to the list (add, remove, reorder)
- mutableStateListOf() does NOT observe changes to elements within the list (ingredient name/icon/stock changes)
What I would like to learn from you guys:
- How would you go about solving this issue?
- What can I do to achieve a recomposition, if any element within the list changes its state?
storageItems: State<List<StorageItem>>
? Do I now useitems(storageItems.value)
in my IngredientsList? Tried that, does not work either, but probably I didn't do exactly what you intended to convey. – HomolographicstorageItems.add(StorageItem(Ingredient("", 0), 0))
andstorageItems.removeLast()
– Homolographic