androidandroid-jetpack-composejetpack-compose-swipe-to-dismiss

Is Compose's swipe-to-dismiss state always remember the old item based on id, even the list has been refresh to newer one?


I have a simple example app that can

If I

It will crash as shown in the GIF below (You can get the code design from here https://github.com/elye/issue_android_jetpack_compose_swipe_to_dismiss_different_data_same_id)

enter image description here

The reason is crashes because, upon Swipe-to-Dismiss the 2 item (of the 2nd time loaded data), the item it found is still the 2 item of the 1st time loaded data.

It does seems dismissState (as shown code below) always remember the 1st time loaded data (instead of the new data loaded)

                val dismissState = rememberDismissState(
                    confirmStateChange = {
                        Log.d("Track", "$item\n${myListState.value.toMutableList()}")
                        viewModel.removeItem(item)
                        true
                    }
                )

Hence this causes the deletion to send the wrong item in for deletion, and thus causes the failure and crash.

The complete LazyColumn and SwipeToDismiss code is as below

       LazyColumn(modifier = Modifier.fillMaxHeight()) {
            items(
                items = myListState.value,
                key = { todoItem -> todoItem.id }
            ) { item ->
                val dismissState = rememberDismissState(
                    confirmStateChange = {
                        viewModel.removeItem(item)
                        true
                    }
                )

                SwipeToDismiss(
                    state = dismissState,
                    background = {
                        dismissState.dismissDirection ?: return@SwipeToDismiss
                        Box(modifier = Modifier.fillMaxSize().background(Color.Red))
                    },
                    dismissContent = {
                        // The row view of each item
                    }
                )
            }
        }

Is this

  1. My issue, is that I miss out on anything to refresh the dismissState upon loading of new data?
  2. A Google Bug, where SwipeToDismiss will always have to work with a list of Unique IDs . Even if the list is refreshed to a new list, it cannot have the same ID that colide with any item of the previous list
    • i.e. if I replace key = { todoItem -> todoItem.id } with key = { todoItem -> todoItem.title }, then it will all be good

Solution

  • rememberDismissState() will remember the confirmStateChange lambda, which is part of the DismissState. In your case, item can change, but the lambda only captures the initial item value, leading to the crash.

    You can use rememberUpdatedState to solve this:

    val currentItem by rememberUpdatedState(item)
    val dismissState = rememberDismissState(
        confirmStateChange = {
            viewModel.removeItem(currentItem)
            true
        }
    )