Artyom Danilin
Artyom Danilin

Reputation: 123

Compose SwipeToDismiss confirmStateChange applies only >= threshold

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
  }
)

Upvotes: 5

Views: 3450

Answers (3)

aj3423
aj3423

Reputation: 2571

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
        }
    }
)

Upvotes: 1

Nucifera
Nucifera

Reputation: 61

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 = {...}
)

Upvotes: 6

Ivan Vashchenko
Ivan Vashchenko

Reputation: 21

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

Upvotes: 1

Related Questions