Reputation: 391
I have a lazyColumn receiving list from viewModel to create items:
@Composable
fun HomeList(
modifier: Modifier = Modifier,
dataList: List<Data>,
listState: LazyListState = rememberLazyListState(),
onClickItem: (Data) -> Unit,
onLongPressItem: (Data) -> Unit
) {
LazyColumn(
modifier = modifier,
state = listState
) {
items(dataList) { data ->
Log.d(TAG, "HomeList: add data $data")
HomeListItem(
data = data,
onClick = onClickItem,
onLongPressed = onLongPressItem
)
}
}
}
The list is wrapped in a stateFlow in viewModel and collected as state in Composable:
// viewModel
...
private val _uiState = MutableStateFlow(
HomeUiState(
...
homeList = listOf()
)
)
val uiState = _uiState as StateFlow<HomeUiState>
...
// HomeScreen
val uiState by viewModel.uiState.collectAsState()
...
HomeList(
modifier = Modifier
.padding(it)
.navigationBarsPadding(),
dataList = uiState.homeList,
listState = listState,
onClickItem = { data ->
...
},
onLongPressItem = {
...
}
)
ViewModel will load data from Room database and filter uninteresting data according to current screen type:
data class HomeUiState(
val homeType: HomeType,
val homeList: List<Data>
)
enum class HomeType {
CREATED,
VIEWED,
STARRED,
ARCHIVED
}
...
viewModelScope.launch(Dispatchers.IO) {
repository.getAllDataFlow().collect { newList ->
Log.d(TAG, "collect new list: $newList")
when (_uiState.value.homeType) {
HomeType.CREATED -> _uiState.update { it.copy(homeList = homeList.filter { data -> data.status == MarkdownData.STATUS_INTERNAL }) }
HomeType.VIEWED -> _uiState.update { it.copy(homeList = homeList.filter { data -> data.status == MarkdownData.STATUS_EXTERNAL }) }
HomeType.STARRED -> _uiState.update { it.copy(homeList = homeList.filter { data -> data.isStarred == MarkdownData.IS_STARRED }) }
HomeType.ARCHIVED -> _uiState.update { it.copy(homeList = homeList.filter { data -> data.status == MarkdownData.STATUS_ARCHIVED }) }
}
}
}
Here is my problem: Each time I delete an Data object in Room database, the flow is expected to emit new value(list of data after deleting one), which is collected and used to update Ui State for recompose. Logs and experiments show that it indeed does: viewModel collects new list, then lazyColumn observes new list and use items
syntax to create composable items, and the deleted item is removed from UI visibly.
However, despite recompose with new data list, the data object of each composable item remains the old one and does not update as expected. For example, if I have a list of a, b, c, d. After deleting a, the list should be b, c, d. List displayed in UI represents b, c, d indeed, but their actual values correspond to old a, b, c, which hinders further operation. But if I refresh the screen(restart app or navigate to other screen then pop back), everything returns to normal.
I wonder if I missed anything that causes lazyColumn items to update UI but not update the actual data of each item. Thanks!
Upvotes: 2
Views: 4819
Reputation: 25312
I think need to add keys in LazyColumn
item as below, that will help to identify items as an unique way.
LazyColumn(
modifier = modifier,
state = listState
) {
items(dataList,
key = { listItem ->
listItem.id // or any other unique
})
{ data ->
Log.d(TAG, "HomeList: add data $data")
HomeListItem(
data = data,
onClick = onClickItem,
onLongPressed = onLongPressItem
)
}
}
Check more on that here how keys is helpful in list: https://developer.android.com/jetpack/compose/lists#item-keys
Also how key helps in recomposition: https://developer.android.com/jetpack/compose/lifecycle#add-info-smart-recomposition
Upvotes: 9