Restore SwipeToDismiss LazyColumn Item to it's original state?
Asked Answered
W

2

11

I am able to do SwipeToDismiss but I want to restore the swiped LazyColumn item back to its original state. I don't want to remove swiped item but want to restore it to its original state.

I am able to achieve this easily in RecyclerView by just calling notifyItemChanged() but can't figure out how to do this in LazyColumn.

Below is my code:

val dataList = remember{ mutableStateListOf<ListItem>()}
for(i in 0..100){
    dataList.add(ListItem("$i", "'$i' is the item number."))
}

LazyColumn(Modifier.fillMaxSize()){
    items(dataList, key = {it.id}){ item ->
        val dismissState = rememberDismissState(
            confirmStateChange = {
                if(it == DismissedToEnd || it == DismissedToStart){
                    Handler(Looper.getMainLooper()).postDelayed({
                        //dataList.remove(item)
                    }, 1000)
                }
                true
            }
        ) 
        SwipeToDismiss(
            state = dismissState,
            directions = setOf(StartToEnd, EndToStart),
            dismissThresholds = { direction ->
                FractionalThreshold(if (direction == StartToEnd || direction == EndToStart) 0.25f else 0.5f)
            },
            background = {
                val direction = dismissState.dismissDirection ?: return@SwipeToDismiss
                val color by animateColorAsState(
                    targetValue = when(dismissState.targetValue){
                        Default -> Color.LightGray
                        DismissedToEnd -> Color.Green
                        DismissedToStart -> Color.Red
                    }
                )
                val icon = when(direction){
                    StartToEnd -> Icons.Default.Done
                    EndToStart -> Icons.Default.Delete
                }
                val scale by animateFloatAsState(
                    if (dismissState.targetValue == Default) 0.8f else 1.2f
                )
                val alignment = when (direction) {
                    StartToEnd -> Alignment.CenterStart
                    EndToStart -> Alignment.CenterEnd
                }
                Box(modifier = Modifier
                    .fillMaxSize()
                    .background(color)
                    .padding(start = 12.dp, end = 12.dp),
                    contentAlignment = alignment
                ){
                    Icon(icon, contentDescription = "Icon", modifier = Modifier.scale(scale))
                }
            },
            dismissContent = {ItemScreen(dismissState = dismissState, item = item)}
        )
    }
}
Wigley answered 28/1, 2022 at 5:44 Comment(0)
A
11

You can wait currentValue to become non Default and reset the state:

According to Thinking in Compose, composable function should be free of side effects - you shouldn't directly reset the state in the composable scope. For such situations you need to use one of special side effect functions, more info can be found in side-effects documentation.

Recomposition can happen many times, up to once a frame during animation, and not using side effect functions will lead to multiple calls, which can cause animation problems.

As DismissState.reset() is a suspend function, LaunchedEffect fits perfectly here: it's already running on a coroutine scope.

if (dismissState.currentValue != DismissValue.Default) {
    LaunchedEffect(Unit) {
        dismissState.reset()
    }
}
Australian answered 28/1, 2022 at 5:55 Comment(5)
Could you explain, why LaunchedEffect(Unit) is used.Wigley
@Wigley check out updated answer.Australian
I don't think this really solves the problem. The issue is that confirmStateChange is inside the definition of the dismiss state so I cannot reference it.Seritaserjeant
@IdhanosiYerimah confirmStateChange is called when user removes the finger from the screen, then, if you return true, it'll animate dismissContent to end dismiss animation. That's why here in my answer I wait dismissState.currentValue to become DismissedToStart before calling reset. But if you don't need to finish dismiss animation, you can return false and it'll animate to the original state. Note that from user perspective it may look like it haven't worked out.Australian
Yes. In my case there was a confirmation dialog so i needed to reset the swipe state incase the user cancelled the operation. Changing the return boolean from true to false fixed it. Thanks @PhilDukhovSeritaserjeant
C
0

If you return false instead of true inside confirmStateChange inside rememberDismissState, then your state will be restored to DEFAULT after swipe

val dismissState = rememberDismissState(
            confirmStateChange = {
                if(it == DismissedToEnd || it == DismissedToStart){
                    Handler(Looper.getMainLooper()).postDelayed({
                        //dataList.remove(item)
                    }, 1000)
                }
                false // here change true to false
            }
        ) 
Cummine answered 10/9, 2024 at 16:25 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.