InteractionSource

Known direct subclasses
MutableInteractionSource

MutableInteractionSource represents a stream of Interactions corresponding to events emitted by a component.


InteractionSource represents a stream of Interactions corresponding to events emitted by a component. These Interactions can be used to change how components appear in different states, such as when a component is pressed or dragged.

A common use case is androidx.compose.foundation.indication, where androidx.compose.foundation.Indication implementations can subscribe to an InteractionSource to draw indication for different Interactions, such as a ripple effect for PressInteraction.Press and a state overlay for DragInteraction.Start.

For simple cases where you are interested in the binary state of an Interaction, such as whether a component is pressed or not, you can use InteractionSource.collectIsPressedAsState and other extension functions that subscribe and return a Boolean representing whether the component is in this state or not.

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsDraggedAsState
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.Text
import androidx.compose.runtime.remember

// Hoist the MutableInteractionSource that we will provide to interactions
val interactionSource = remember { MutableInteractionSource() }

// Provide the MutableInteractionSource instances to the interactions we want to observe state
// changes for
val draggable = Modifier.draggable(
    interactionSource = interactionSource,
    orientation = Orientation.Horizontal,
    state = rememberDraggableState { /* update some business state here */ }
)

val clickable = Modifier.clickable(
    interactionSource = interactionSource,
    indication = LocalIndication.current
) { /* update some business state here */ }

// Observe changes to the binary state for these interactions
val isDragged by interactionSource.collectIsDraggedAsState()
val isPressed by interactionSource.collectIsPressedAsState()

// Use the state to change our UI
val (text, color) = when {
    isDragged && isPressed -> "Dragged and pressed" to Color.Red
    isDragged -> "Dragged" to Color.Green
    isPressed -> "Pressed" to Color.Blue
    // Default / baseline state
    else -> "Drag me horizontally, or press me!" to Color.Black
}

Box(
    Modifier
        .fillMaxSize()
        .wrapContentSize()
        .size(width = 240.dp, height = 80.dp)
) {
    Box(
        Modifier
            .fillMaxSize()
            .then(clickable)
            .then(draggable)
            .border(BorderStroke(3.dp, color))
            .padding(3.dp)
    ) {
        Text(
            text, style = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
            modifier = Modifier.fillMaxSize().wrapContentSize()
        )
    }
}

For more complex cases, such as when building an androidx.compose.foundation.Indication, the order of the events can change how a component / indication should be drawn. For example, if a component is being dragged and then becomes focused, the most recent Interaction is FocusInteraction.Focus, so the component should appear in a focused state to signal this event to the user.

InteractionSource exposes interactions to support these use cases - a Flow representing the stream of all emitted Interactions. This also provides more information, such as the press position of PressInteraction.Press, so you can show an effect at the specific point the component was pressed, and whether the press was PressInteraction.Release or PressInteraction.Cancel, for cases when a component should behave differently if the press was released normally or interrupted by another gesture.

You can collect from interactions as you would with any other Flow:

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.Text
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember

// Hoist the MutableInteractionSource that we will provide to interactions
val interactionSource = remember { MutableInteractionSource() }

// Provide the MutableInteractionSource instances to the interactions we want to observe state
// changes for
val draggable = Modifier.draggable(
    interactionSource = interactionSource,
    orientation = Orientation.Horizontal,
    state = rememberDraggableState { /* update some business state here */ }
)

val clickable = Modifier.clickable(
    interactionSource = interactionSource,
    // This component is a compound component where part of it is clickable and part of it is
    // draggable. As a result we want to show indication for the _whole_ component, and not
    // just for clickable area. We set `null` indication here and provide an explicit
    // Modifier.indication instance later that will draw indication for the whole component.
    indication = null
) { /* update some business state here */ }

// SnapshotStateList we will use to track incoming Interactions in the order they are emitted
val interactions = remember { mutableStateListOf<Interaction>() }

// Collect Interactions - if they are new, add them to `interactions`. If they represent stop /
// cancel events for existing Interactions, remove them from `interactions` so it will only
// contain currently active `interactions`.
LaunchedEffect(interactionSource) {
    interactionSource.interactions.collect { interaction ->
        when (interaction) {
            is PressInteraction.Press -> interactions.add(interaction)
            is PressInteraction.Release -> interactions.remove(interaction.press)
            is PressInteraction.Cancel -> interactions.remove(interaction.press)
            is DragInteraction.Start -> interactions.add(interaction)
            is DragInteraction.Stop -> interactions.remove(interaction.start)
            is DragInteraction.Cancel -> interactions.remove(interaction.start)
        }
    }
}

// Display some text based on the most recent Interaction stored in `interactions`
val text = when (interactions.lastOrNull()) {
    is DragInteraction.Start -> "Dragged"
    is PressInteraction.Press -> "Pressed"
    else -> "No state"
}

Column(
    Modifier
        .fillMaxSize()
        .wrapContentSize()
) {
    Row(
        // Draw indication for the whole component, based on the Interactions dispatched by
        // our hoisted MutableInteractionSource
        Modifier.indication(
            interactionSource = interactionSource,
            indication = LocalIndication.current
        )
    ) {
        Box(
            Modifier
                .size(width = 240.dp, height = 80.dp)
                .then(clickable)
                .border(BorderStroke(3.dp, Color.Blue))
                .padding(3.dp)
        ) {
            val pressed = interactions.any { it is PressInteraction.Press }
            Text(
                text = if (pressed) "Pressed" else "Not pressed",
                style = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
                modifier = Modifier.fillMaxSize().wrapContentSize()
            )
        }
        Box(
            Modifier
                .size(width = 240.dp, height = 80.dp)
                .then(draggable)
                .border(BorderStroke(3.dp, Color.Red))
                .padding(3.dp)
        ) {
            val dragged = interactions.any { it is DragInteraction.Start }
            Text(
                text = if (dragged) "Dragged" else "Not dragged",
                style = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
                modifier = Modifier.fillMaxSize().wrapContentSize()
            )
        }
    }
    Text(
        text = text,
        style = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
        modifier = Modifier.fillMaxSize().wrapContentSize()
    )
}

To emit Interactions so that consumers can react to them, see MutableInteractionSource.

Summary

Public properties

Flow<Interaction>

Flow representing the stream of all Interactions emitted through this InteractionSource.

Cmn

Extension functions

State<Boolean>

Subscribes to this MutableInteractionSource and returns a State representing whether this component is dragged or not.

Cmn
State<Boolean>

Subscribes to this MutableInteractionSource and returns a State representing whether this component is focused or not.

Cmn
State<Boolean>

Subscribes to this MutableInteractionSource and returns a State representing whether this component is hovered or not.

Cmn
State<Boolean>

Subscribes to this MutableInteractionSource and returns a State representing whether this component is pressed or not.

Cmn

Public properties

interactions

val interactionsFlow<Interaction>

Flow representing the stream of all Interactions emitted through this InteractionSource. This can be used to see Interactions emitted in order, and with additional metadata, such as the press position for PressInteraction.Press.

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.Text
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember

// Hoist the MutableInteractionSource that we will provide to interactions
val interactionSource = remember { MutableInteractionSource() }

// Provide the MutableInteractionSource instances to the interactions we want to observe state
// changes for
val draggable = Modifier.draggable(
    interactionSource = interactionSource,
    orientation = Orientation.Horizontal,
    state = rememberDraggableState { /* update some business state here */ }
)

val clickable = Modifier.clickable(
    interactionSource = interactionSource,
    // This component is a compound component where part of it is clickable and part of it is
    // draggable. As a result we want to show indication for the _whole_ component, and not
    // just for clickable area. We set `null` indication here and provide an explicit
    // Modifier.indication instance later that will draw indication for the whole component.
    indication = null
) { /* update some business state here */ }

// SnapshotStateList we will use to track incoming Interactions in the order they are emitted
val interactions = remember { mutableStateListOf<Interaction>() }

// Collect Interactions - if they are new, add them to `interactions`. If they represent stop /
// cancel events for existing Interactions, remove them from `interactions` so it will only
// contain currently active `interactions`.
LaunchedEffect(interactionSource) {
    interactionSource.interactions.collect { interaction ->
        when (interaction) {
            is PressInteraction.Press -> interactions.add(interaction)
            is PressInteraction.Release -> interactions.remove(interaction.press)
            is PressInteraction.Cancel -> interactions.remove(interaction.press)
            is DragInteraction.Start -> interactions.add(interaction)
            is DragInteraction.Stop -> interactions.remove(interaction.start)
            is DragInteraction.Cancel -> interactions.remove(interaction.start)
        }
    }
}

// Display some text based on the most recent Interaction stored in `interactions`
val text = when (interactions.lastOrNull()) {
    is DragInteraction.Start -> "Dragged"
    is PressInteraction.Press -> "Pressed"
    else -> "No state"
}

Column(
    Modifier
        .fillMaxSize()
        .wrapContentSize()
) {
    Row(
        // Draw indication for the whole component, based on the Interactions dispatched by
        // our hoisted MutableInteractionSource
        Modifier.indication(
            interactionSource = interactionSource,
            indication = LocalIndication.current
        )
    ) {
        Box(
            Modifier
                .size(width = 240.dp, height = 80.dp)
                .then(clickable)
                .border(BorderStroke(3.dp, Color.Blue))
                .padding(3.dp)
        ) {
            val pressed = interactions.any { it is PressInteraction.Press }
            Text(
                text = if (pressed) "Pressed" else "Not pressed",
                style = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
                modifier = Modifier.fillMaxSize().wrapContentSize()
            )
        }
        Box(
            Modifier
                .size(width = 240.dp, height = 80.dp)
                .then(draggable)
                .border(BorderStroke(3.dp, Color.Red))
                .padding(3.dp)
        ) {
            val dragged = interactions.any { it is DragInteraction.Start }
            Text(
                text = if (dragged) "Dragged" else "Not dragged",
                style = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
                modifier = Modifier.fillMaxSize().wrapContentSize()
            )
        }
    }
    Text(
        text = text,
        style = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
        modifier = Modifier.fillMaxSize().wrapContentSize()
    )
}

Extension functions

collectIsDraggedAsState

@Composable
fun InteractionSource.collectIsDraggedAsState(): State<Boolean>

Subscribes to this MutableInteractionSource and returns a State representing whether this component is dragged or not.

DragInteraction is typically set by interactions such as androidx.compose.foundation.gestures.draggable and androidx.compose.foundation.gestures.scrollable, and higher level components such as androidx.compose.foundation.lazy.LazyRow, available through androidx.compose.foundation.lazy.LazyListState.interactionSource.

Returns
State<Boolean>

State representing whether this component is being dragged or not

collectIsFocusedAsState

@Composable
fun InteractionSource.collectIsFocusedAsState(): State<Boolean>

Subscribes to this MutableInteractionSource and returns a State representing whether this component is focused or not.

FocusInteraction is typically set by androidx.compose.foundation.focusable and focusable components, such as androidx.compose.foundation.text.BasicTextField.

Returns
State<Boolean>

State representing whether this component is being focused or not

collectIsHoveredAsState

@Composable
fun InteractionSource.collectIsHoveredAsState(): State<Boolean>

Subscribes to this MutableInteractionSource and returns a State representing whether this component is hovered or not.

HoverInteraction is typically set by androidx.compose.foundation.hoverable and hoverable components.

Returns
State<Boolean>

State representing whether this component is being hovered or not

collectIsPressedAsState

@Composable
fun InteractionSource.collectIsPressedAsState(): State<Boolean>

Subscribes to this MutableInteractionSource and returns a State representing whether this component is pressed or not.

PressInteraction is typically set by androidx.compose.foundation.clickable and clickable higher level components, such as buttons.

Returns
State<Boolean>

State representing whether this component is being pressed or not