androidkotlinandroid-jetpack-composekotlin-flowandroid-jetpack-compose-list

Jetpack compose list wrong item selected after reordering or filtering


I have a ViewModel that produces a StateFlow like this:

private val _profiles = MutableStateFlow<List<ProfileSnap>>(listOf())
val profiles: StateFlow<List<ProfileSnap>>
  get() = _profiles

Values are updated in another fun:

private fun loadProfiles() = viewModelScope.launch {
   _profiles.value = profileDao.getAll(profilesSearch, profilesSort)
}

Finally, in Compose I list all values (this is a simplified version of my code):

@Composable
fun SetContent(viewModel: ProfilesViewModel){
   val profiles = viewModel.profiles.collectAsState()
   LazyColumn(
      modifier = Modifier
         .fillMaxHeight()
   ) {
      itemsIndexed(items = profiles.value) { _, profile ->
         Text(
            text = "(${profile.profileId}) ${profile.label}",
            modifier = Modifier
            .pointerInput(Unit) {
               detectTapGestures(
                  onLongPress = {
                     Log.d(TAG, "onLongPress: ${profile.profileId}")
                  },
                  onTap = {
                     Log.d(TAG, "onTap: ${profile.profileId}")
                  },
               )
            }   
         ) 
      }
   }    
}

At the beginning, when I reach the list fragment and I click on an element, I get the correct corresponding profileId. But, when I apply a filter or I change the list sorting and the loadProfiles() function is called:

  1. the list correctly changes accordingly to the new filtered and/sorted profiles
  2. when I click on an element I get the wrong profileId, I seems the one of the previous list disposition!

What am I doing wrong? profiles are not up to date? But if they are not updated, why the list is "graphically" correct? Here what happens:

(1) A
-----
(2) B   
-----
(3) C   <== CLICK - onTap: 3 / LONGPRESS - onLongPress: 3

Change sort order:

(3) C
-----
(2) B   
-----
(1) A   <== CLICK - onTap: 3 [should has been 1] / LONGPRESS - onLongPress: 3 [should has been 1]

Thank you very much


Solution

  • You can check the official doc:

    By default, each item's state is keyed against the position of the item in the list. However, this can cause issues if the data set changes, since items which change position effectively lose any remembered state. If you imagine the scenario of LazyRow within a LazyColumn, if the row changes item position, the user would then lose their scroll position within the row.

    To combat this, you can provide a stable and unique key for each item, providing a block to the key parameter. Providing a stable key enables item state to be consistent across data-set changes:

    LazyColumn() {
          items(
                items = profiles.value,
                key = { profile ->
                        // Return a stable + unique key for the item
                        profile.profileId
                    }
          ) { profile ->
             //....
          }
       }