Migrate from Swipeable to AnchoredDraggable

Swipeable is a Compose Material API that helps you build components that can be swiped between discrete states, such as bottom sheets, drawers, or swipe-to-dismiss. To better support advanced use cases, such as anchors that depend on the size of a component, a successor was published in Compose-Foundation 1.6.0-alpha01: AnchoredDraggable. AnchoredDraggable is a Foundation API for building draggable components with anchored states, such as bottom sheets, drawers, or swipe-to-dismiss.

Material's Swipeable APIs have been deprecated in favor of Foundation's AnchoredDraggable and will be removed in a future release. This guide describes how to migrate from Swipeable APIs to AnchoredDraggable.

Migrate SwipeableState to AnchoredDraggableState

Start by identifying changes to your state holder. AnchoredDraggableState cannot be inherited from, and the offset is represented as Float.NaN before it is initialized.

Update your state holder

AnchoredDraggableState is a final class, meaning it cannot be inherited from. If your existing component inherits from SwipeableState, update your state holder to hold a reference to the AnchoredDraggableState instead of inheriting from it:

Swipeable

class MySwitchState: SwipeableState()

AnchoredDraggable

class MySwitchState {
    private val anchoredDraggableState = AnchoredDraggableState(...)
}

Since your state holder does not inherit from SwipeableState anymore, you might have to expose APIs yourself. The most common APIs you can use are offset, progress, currentValue and targetValue.

Access the offset

Unlike in Swipeable, AnchoredDraggableState's offset is Float.NaN before it is initialized. In AnchoredDraggable, the anchors can be passed to AnchoredDraggableState's constructor or updated through AnchoredDraggableState#updateAnchors. Passing the anchors to AnchoredDraggableState's constructor initializes the offset immediately.

If your anchors depend on layout or could change, use AnchoredDraggableState#updateAnchors to avoid recreating the state when the anchors change.

If you use updateAnchors, the offset will be Float.NaN before passing the anchors to updateAnchors. To avoid accidentally passing Float.NaN to components, use AnchoredDraggableState#requireOffset to require that the offset has been initialized when reading it. This helps you catch inconsistencies or possible bugs early on.

@Composable
fun AnchoredDraggableBox() {
    val state = remember { AnchoredDraggableState(...) }
    val density = LocalDensity.current
    val anchors = remember { DraggableAnchors { ... } }
    SideEffect {
        state.updateAnchors(anchors)
    }
    Box(
        Modifier.offset { IntOffset(x = state.requireOffset(), y = 0) }
    }
}

Migrate Modifier.swipeable to Modifier.anchoredDraggable

Modifier.anchoredDraggable() replaces Modifier.swipeable. Some of Modifier.swipeable()'s parameters have moved to AnchoredDraggableState directly, as described in the following sections.

Define anchors

Define the anchors using the DraggableAnchors builder method. Then, pass them to AnchoredDraggableState#updateAnchors or AnchoredDraggableState's constructor:

Constructor

enum class DragValue { Start, Center, End }

@Composable
fun AnchoredDraggableBox() {
    val anchors = DraggableAnchors {
        Start at -100.dp.toPx()
        Center at 0f
        End at 100.dp.toPx()
    }
    val state = remember {
        AnchoredDraggableState(anchors = anchors)
    }
    Box(
        Modifier.offset { IntOffset(x = state.requireOffset(), y = 0) }
    )
}

updateAnchors

enum class DragValue { Start, Center, End }

@Composable
fun AnchoredDraggableBox() {
    val state = remember { AnchoredDraggableState(...) }
    val density = LocalDensity.current
    val anchors = with (density) {
        DraggableAnchors {
            Start at -100.dp.toPx()
            Center at 0f
            End at 100.dp.toPx()
        }
    }
    SideEffect {
        state.updateAnchors(anchors)
    }
    Box(
        Modifier.offset { IntOffset(x = state.requireOffset(), y = 0) }
    )
}

If the anchors are static, pass them to the constructor. If they depend on layout, or are not static, use updateAnchors.

Define positional thresholds

The type and name of the thresholds parameter has changed. Instead of having a separate ThresholdConfig interface, AnchoredDraggableState has a positionalThreshold parameter that takes a lambda function that returns the position of the threshold. For example, a positional threshold of 50% could be expressed as:

val anchoredDraggableState = AnchoredDraggableState(
    positionalThreshold = { distance -> distance * 0.5f },
    ...
)

A positional threshold of 56dp could be expressed as:

val density = LocalDensity.current
val anchoredDraggableState = AnchoredDraggableState(
    positionalThreshold = { with(density) { 56.dp.toPx() } },
    ...
)

Define velocity thresholds

Velocity thresholds are also passed to AnchoredDraggableState's constructor, and also expressed as a lambda:

val density = LocalDensity.current
val anchoredDraggableState = AnchoredDraggableState(
    velocityThreshold = { with(density) { 125.dp.toPx() } },
    ...
)

Changes to the API surface

Find an overview of changes to the API surface below.

AnchoredDraggableState

SwipeableState

AnchoredDraggableState

open class SwipeableState(initialValue: T, animationSpec: AnimationSpec = …, confirmStateChange: (T) -> Boolean = …)

class AnchoredDraggableState( initialValue: T, animationSpec: AnimationSpec = …, confirmValueChange: (T) -> Boolean = …, positionalThreshold: Density.(Float) -> Float = …, velocityThreshold: Dp = …)

offset: State

offset: Float
requireOffset()

progress: SwipeProgress

progress: Float [0f..1f]

currentValue: T

currentValue: T

targetValue: T

targetValue: T

direction: Float [-1f, 0f, 1f]

N/A

suspend animateTo(
targetValue: T,
anim: AnimationSpec = …)

suspend animateTo(
targetState: T,
velocity: Float =
lastVelocity)

suspend snapTo(targetValue: T)

suspend snapTo(targetValue: T)

performDrag(delta: Float)

dispatchRawDelta(delta: Float)

suspend performFling(velocity: Float)

suspend settle(velocity: Float)

isAnimationRunning: Boolean

isAnimationRunning: Boolean

lastVelocity: Float

Modifier.anchoredDraggable

Modifier.swipeable

Modifier.anchoredDraggable

state: SwipeableState

state: AnchoredDraggableState

anchors: Map

AnchoredDraggableState#updateAnchors
or

AnchoredDraggableState#constructor

orientation: Orientation

orientation: Orientation

enabled: Boolean = true

enabled: Boolean = true

reverseDirection: Boolean = false

reverseDirection: Boolean = false

interactionSource: MutableInteractionSource? = null

interactionSource: MutableInteractionSource? = null

thresholds: (from: T, to: T) -> ThresholdConfig = FixedThreshold(56.dp)

Passed to AnchoredDraggableState constructor as positionalThreshold

resistance: ResistanceConfig? = …

Not yet supported. See b/288084801 for the latest status.

velocityThreshold: Dp = 125.dp

Passed to AnchoredDraggable constructor