Compose SwipeToDismiss confirmStateChange applies only >= threshold
Asked Answered
R

3

5

I have a SwipeToDismiss instance to delete items with dismissThresholds 75%. If user swipes row too fast without reaching 75% threshold the row being deleted. How to prevent that?

Here is code where I execute an action:

val dismissState = rememberDismissState(
  confirmStateChange = {
    if (it == DismissValue.DismissedToStart) {
      viewModel.deleteCity(city)
    }
    true
  }
)
Reeder answered 19/6, 2022 at 11:35 Comment(2)
I get the same issue. Did you find a fix ?Ube
No, @Ube i still don't know how to do thatReeder
L
6

I ran into the same issue and found a fix that works for me. My theory is, that when the swipe is very fast but doesn't go very far (doesn't reach the set fractional threshold), the layout just immediately resets. In this case (DismissToStart), meaning the view snaps back to the right screen edge, giving us the value of 1.0f for the threshold and thus triggering the confirmStateChange because the fraction is per definition higher than our threshold. The problem being that our threshold is measured from the right screen edge, and this fracion (in my theory) is measured from the left screen edge.

So my solution is to track the current fractional value, and inside confirmStateChange check if the current value is higher than the threshold, BUT NOT 1.0f. In a real world scenario, I think it's impossible to reach 1.0 with actual swiping the finger from right to left, so the solution seems safe to me.

val dismissThreshold = 0.25f
val currentFraction = remember { mutableStateOf(0f) }

val dismissState = rememberDismissState(
    confirmStateChange = {
        if (it == DismissValue.DismissedToStart) {
            if (currentFraction.value >= dismissThreshold && currentFraction.value < 1.0f) {
                onSwiped(item)
            }
        }
        dismissOnSwipe
    }
)

SwipeToDismiss(
    state = dismissState,
    modifier = Modifier.animateItemPlacement(),
    directions = setOf(DismissDirection.EndToStart),
    dismissThresholds = { direction ->
        FractionalThreshold(dismissThreshold)
    },
    background = {
        Box(...) {
            currentFraction.value = dismissState.progress.fraction
            ...
        }
    }
    dismissContent = {...}
)
Lillian answered 22/11, 2022 at 10:12 Comment(1)
I utilized your solution, and it's working perfectly. However, I suggest setting dismissThreshold to be more than 0.4f, as, during testing, I found multiple very fast swipe events which went past the 0.25f value that you used.Preciousprecipice
I
1

I'm dealing with the same issue, it seems to be a feature, not a bug, but I did not find the way to disable it.

*If the user has dragged their finger across over 50% of the screen width, the app should trigger the rest of the swipe back animation. If it's less than that, the app should snap back to the full app view.

If the gesture is quick, ignore the 50% threshold rule and swipe back.*

https://developer.android.com/training/wearables/compose/swipe-to-dismiss

Innis answered 11/11, 2022 at 8:23 Comment(0)
M
1

A less intrusive version based on Nucifera's solution, without touching the background { }:

const val Threshold = 0.5f

var state: SwipeToDismissBoxState? = null // workaround for the recursive reference
state = rememberSwipeToDismissBoxState(
    positionalThreshold = {
        it * Threshold
    },
    confirmValueChange = {
        if (it == EndToStart && state!!.progress > Threshold) {
            // ...
            true
        } else {
            false
        }
    }
)
Mckissick answered 7/9 at 12:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.