androidkotlinandroid-jetpack-composeandroid-jetpack-compose-lazy-column

ModalBottomSheet in LazyColumn contains wrong data


I have a list with a few records. Every record has a city, country, date and id (its primary key). In dao, then repository I have function which accepts id. Finally display list it's fine, but problem is when I click the record and display modal with this data. Id records is incorrect and the city name, country name etc. is incorrect.

Repository: override suspend fun showDetailsTrip(id: Int) = db.dao().showDetailsTrip(id)

viewModel:

private fun showDetails(id: Int) {
        _uiState.update { it.copy(showBottomSheet = true) }
        viewModelScope.launch {
            repository.showDetailsTrip(id = id)
        }
    }

Screen:

LazyColumn(
        modifier = Modifier
            .padding(MaterialTheme.whereNowSpacing.space16)
            .fillMaxSize(),
        verticalArrangement = Arrangement.spacedBy(MaterialTheme.whereNowSpacing.space32),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        items(items = state.tripList, key = { id -> id.id }) { list ->
            WhereNowDetailsTile(
                city = list.cityFrom,
                country = list.countryFrom,
                date = list.date,
                timeTravel = LocalDate.now(),
                countDays = 0,
                onDeleteClick = { uiIntent(TripListUiIntent.OnDeleteTrip(list.id)) },
                **onClick = { uiIntent(TripListUiIntent.ShowTripDetails(list.id)) }**
            )

            if (state.showBottomSheet) {
                ModalBottomSheet(
                    onDismissRequest = { uiIntent(TripListUiIntent.HideTripDetails) },
                    contentColor = MaterialTheme.colorScheme.error,
                    sheetState = sheetState,
                    tonalElevation = 72.dp,
                    dragHandle = {}
                ) {
                    Column(
                        modifier = Modifier
                            .background(MaterialTheme.colorScheme.background)
                            .padding(vertical = MaterialTheme.whereNowSpacing.space24)
                            .padding(horizontal = MaterialTheme.whereNowSpacing.space16)
                            .verticalScroll(rememberScrollState())
                    ) {
                        Text(
                            modifier = Modifier
                                .align(Alignment.CenterHorizontally),
                            text = "Wylot",
                            style = MaterialTheme.typography.titleLarge.copy(MaterialTheme.colorScheme.primary)
                        )
                        HorizontalDivider(
                            modifier = Modifier.padding(
                                vertical = MaterialTheme.whereNowSpacing.space16,
                                horizontal = MaterialTheme.whereNowSpacing.space8
                            )
                        )

                        **WhereNowTextField(
                            label = "City",
                            value = state.tripList.find { it.id == list.id }?.cityFrom ?: ""
                        )**
    ```


Solution

  • The ModalBottomSheet is part of the LazyColumn and each item has its own ModalBottomSheet. When showBottomSheet is set to true all ModalBottomSheets are displayed. I'm not sure if there is a defined order in which they are displayed, but since they are modal and don't interfer with the layout they will simply be drawn over each other, with one of them (probably the last) ending up on top and being the only one that is visible. That will most likely not be the one you want, hence the incorrect data you observe.

    To solve this you need to move the ModalBottomSheet out of the LazyColumn, f.e. directly after it. Replace showBottomSheet in your UiState class with this:

    val detailsId: Int? = null,
    

    which is set in the view model's showDetails accordingly:

    _uiState.update { it.copy(detailsId = id) }
    

    Reset it to null when you process the HideTripDetails intent.

    The condition to display the ModalBottomSheet must be changed from state.showBottomSheet to state.detailsId != null and the call to WhereNowTextField can now directly access this id:

    WhereNowTextField(
        label = "City",
        value = state.tripList.find { it.id == state.detailsId }?.cityFrom ?: "",
    )
    

    That's it, only one ModalBottomSheet should now be displayed, containing the correct values.