InteractionSource

Interface

Common
interface InteractionSource

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 State representing whether the component is in this state or not.

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:

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

Properties

Common
val interactions: Flow<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.

Code Examples

SimpleInteractionSourceSample

@Composable
fun SimpleInteractionSourceSample() {
    // 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(),
            )
        }
    }
}

InteractionSourceFlowSample

@Composable
fun InteractionSourceFlowSample() {
    // 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(),
        )
    }
}