Google is committed to advancing racial equity for Black communities. See how.

InteractionState

@Stable class InteractionState : State<Set<Interaction>>
kotlin.Any
   ↳ androidx.compose.foundation.InteractionState

InteractionState represents a Set of Interactions present on a given component. This allows you to build higher level components comprised of lower level interactions such as clickable and androidx.compose.foundation.gestures.draggable, and react to Interaction changes driven by these components in one place. For Interactions with an associated position, such as Interaction.Pressed, you can retrieve this position by using interactionPositionFor.

Creating an InteractionState and passing it to these lower level interactions will cause a recomposition when there are changes to the state of Interaction, such as when a clickable becomes Interaction.Pressed.

For cases when you are only interested in one Interaction, or you have a priority for cases when multiple Interactions are present, you can use contains, such as in the following example:

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.InteractionState
import androidx.compose.foundation.Text
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.preferredSize
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.remember

val interactionState = remember { InteractionState() }

val draggable = Modifier.draggable(
    orientation = Orientation.Horizontal,
    interactionState = interactionState
) { /* update some business state here */ }

// Use InteractionState to determine how this component should appear during transient UI states
// In this example we are using a 'priority' system, such that we ignore multiple states, and
// don't care about the most recent state - Dragged is more important than Pressed.
val (text, color) = when {
    Interaction.Dragged in interactionState -> "Dragged" to Color.Red
    Interaction.Pressed in interactionState -> "Pressed" to Color.Blue
    // Default / baseline state
    else -> "Drag me horizontally, or press me!" to Color.Black
}

Box(
    Modifier
        .fillMaxSize()
        .wrapContentSize()
        .preferredSize(width = 240.dp, height = 80.dp)
) {
    Box(
        Modifier
            .fillMaxSize()
            .clickable(interactionState = interactionState) { /* do nothing */ }
            .then(draggable)
            .border(BorderStroke(3.dp, color))
            .padding(3.dp)
    ) {
        Text(
            text, style = AmbientTextStyle.current.copy(textAlign = TextAlign.Center),
            modifier = Modifier.fillMaxSize().wrapContentSize()
        )
    }
}
Often it is important to respond to the most recently added Interaction, as this correspondsto the user's most recent interaction with a component. To enable such cases, value isguaranteed to have its ordering preserved, with the most recent Interaction added to the end.As a result, you can simply iterate / filter value from the end, until you find anInteraction you are interested in, such as in the following example:
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.InteractionState
import androidx.compose.foundation.Text
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.draggable
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.preferredSize
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.remember

val interactionState = remember { InteractionState() }

val draggable = Modifier.draggable(
    orientation = Orientation.Horizontal,
    interactionState = interactionState
) { /* update some business state here */ }

val clickable = Modifier.clickable(interactionState = interactionState) {
    /* update some business state here */
}

// In this example we have a complex component that can be in multiple states at the same time
// (both pressed and dragged, since different areas of the same component can be pressed and
// dragged at the same time), and we want to use only the most recent state to show the visual
// state of the component. This could be with a visual overlay, or similar. Note that the most
// recent state is the _last_ state added to interactionState, so we want to start from the end,
// hence we use `lastOrNull` and not `firstOrNull`.
val latestState = interactionState.value.lastOrNull {
    // We only care about pressed and dragged states here, so ignore everything else
    it is Interaction.Dragged || it is Interaction.Pressed
}

val text = when (latestState) {
    Interaction.Dragged -> "Dragged"
    Interaction.Pressed -> "Pressed"
    else -> "No state"
}

Column(
    Modifier
        .fillMaxSize()
        .wrapContentSize()
) {
    Row {
        Box(
            Modifier
                .preferredSize(width = 240.dp, height = 80.dp)
                .then(clickable)
                .border(BorderStroke(3.dp, Color.Blue))
                .padding(3.dp)
        ) {
            val pressed = Interaction.Pressed in interactionState
            Text(
                text = if (pressed) "Pressed" else "Not pressed",
                style = AmbientTextStyle.current.copy(textAlign = TextAlign.Center),
                modifier = Modifier.fillMaxSize().wrapContentSize()
            )
        }
        Box(
            Modifier
                .preferredSize(width = 240.dp, height = 80.dp)
                .then(draggable)
                .border(BorderStroke(3.dp, Color.Red))
                .padding(3.dp)
        ) {
            val dragged = Interaction.Dragged in interactionState
            Text(
                text = if (dragged) "Dragged" else "Not dragged",
                style = AmbientTextStyle.current.copy(textAlign = TextAlign.Center),
                modifier = Modifier.fillMaxSize().wrapContentSize()
            )
        }
    }
    Text(
        text = text,
        style = AmbientTextStyle.current.copy(textAlign = TextAlign.Center),
        modifier = Modifier.fillMaxSize().wrapContentSize()
    )
}

Summary

Public constructors

InteractionState represents a Set of Interactions present on a given component.

Public methods
Unit
addInteraction(interaction: Interaction, position: Offset? = null)

Adds the provided interaction to this InteractionState.

operator Boolean
contains(interaction: Interaction)

Offset?

Returns the position for a particular Interaction, if there is a position associated with the interaction.

Unit

Removes the provided interaction, if it is present, from this InteractionState.

Inherited extension functions
From androidx.compose.runtime
operator T
State<T>.getValue(thisObj: Any?, property: KProperty<*>)

Permits property delegation of vals using by for State.

Properties
Set<Interaction>

The Set containing all Interactions present in this InteractionState.

Public constructors

<init>

InteractionState()

InteractionState represents a Set of Interactions present on a given component. This allows you to build higher level components comprised of lower level interactions such as clickable and androidx.compose.foundation.gestures.draggable, and react to Interaction changes driven by these components in one place. For Interactions with an associated position, such as Interaction.Pressed, you can retrieve this position by using interactionPositionFor.

Creating an InteractionState and passing it to these lower level interactions will cause a recomposition when there are changes to the state of Interaction, such as when a clickable becomes Interaction.Pressed.

For cases when you are only interested in one Interaction, or you have a priority for cases when multiple Interactions are present, you can use contains, such as in the following example:

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.InteractionState
import androidx.compose.foundation.Text
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.preferredSize
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.remember

val interactionState = remember { InteractionState() }

val draggable = Modifier.draggable(
    orientation = Orientation.Horizontal,
    interactionState = interactionState
) { /* update some business state here */ }

// Use InteractionState to determine how this component should appear during transient UI states
// In this example we are using a 'priority' system, such that we ignore multiple states, and
// don't care about the most recent state - Dragged is more important than Pressed.
val (text, color) = when {
    Interaction.Dragged in interactionState -> "Dragged" to Color.Red
    Interaction.Pressed in interactionState -> "Pressed" to Color.Blue
    // Default / baseline state
    else -> "Drag me horizontally, or press me!" to Color.Black
}

Box(
    Modifier
        .fillMaxSize()
        .wrapContentSize()
        .preferredSize(width = 240.dp, height = 80.dp)
) {
    Box(
        Modifier
            .fillMaxSize()
            .clickable(interactionState = interactionState) { /* do nothing */ }
            .then(draggable)
            .border(BorderStroke(3.dp, color))
            .padding(3.dp)
    ) {
        Text(
            text, style = AmbientTextStyle.current.copy(textAlign = TextAlign.Center),
            modifier = Modifier.fillMaxSize().wrapContentSize()
        )
    }
}
Often it is important to respond to the most recently added Interaction, as this correspondsto the user's most recent interaction with a component. To enable such cases, value isguaranteed to have its ordering preserved, with the most recent Interaction added to the end.As a result, you can simply iterate / filter value from the end, until you find anInteraction you are interested in, such as in the following example:
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.InteractionState
import androidx.compose.foundation.Text
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.draggable
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.preferredSize
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.remember

val interactionState = remember { InteractionState() }

val draggable = Modifier.draggable(
    orientation = Orientation.Horizontal,
    interactionState = interactionState
) { /* update some business state here */ }

val clickable = Modifier.clickable(interactionState = interactionState) {
    /* update some business state here */
}

// In this example we have a complex component that can be in multiple states at the same time
// (both pressed and dragged, since different areas of the same component can be pressed and
// dragged at the same time), and we want to use only the most recent state to show the visual
// state of the component. This could be with a visual overlay, or similar. Note that the most
// recent state is the _last_ state added to interactionState, so we want to start from the end,
// hence we use `lastOrNull` and not `firstOrNull`.
val latestState = interactionState.value.lastOrNull {
    // We only care about pressed and dragged states here, so ignore everything else
    it is Interaction.Dragged || it is Interaction.Pressed
}

val text = when (latestState) {
    Interaction.Dragged -> "Dragged"
    Interaction.Pressed -> "Pressed"
    else -> "No state"
}

Column(
    Modifier
        .fillMaxSize()
        .wrapContentSize()
) {
    Row {
        Box(
            Modifier
                .preferredSize(width = 240.dp, height = 80.dp)
                .then(clickable)
                .border(BorderStroke(3.dp, Color.Blue))
                .padding(3.dp)
        ) {
            val pressed = Interaction.Pressed in interactionState
            Text(
                text = if (pressed) "Pressed" else "Not pressed",
                style = AmbientTextStyle.current.copy(textAlign = TextAlign.Center),
                modifier = Modifier.fillMaxSize().wrapContentSize()
            )
        }
        Box(
            Modifier
                .preferredSize(width = 240.dp, height = 80.dp)
                .then(draggable)
                .border(BorderStroke(3.dp, Color.Red))
                .padding(3.dp)
        ) {
            val dragged = Interaction.Dragged in interactionState
            Text(
                text = if (dragged) "Dragged" else "Not dragged",
                style = AmbientTextStyle.current.copy(textAlign = TextAlign.Center),
                modifier = Modifier.fillMaxSize().wrapContentSize()
            )
        }
    }
    Text(
        text = text,
        style = AmbientTextStyle.current.copy(textAlign = TextAlign.Center),
        modifier = Modifier.fillMaxSize().wrapContentSize()
    )
}

Public methods

addInteraction

fun addInteraction(
    interaction: Interaction,
    position: Offset? = null
): Unit

Adds the provided interaction to this InteractionState. Since InteractionState represents a Set, duplicate interactions will not be added, and hence will not cause a recomposition.

Parameters
interaction: Interaction interaction to add
position: Offset? = null position at which the interaction occurred, if relevant. For example, for Interaction.Pressed, this will be the position of the pointer input that triggered the pressed state.

contains

operator fun contains(interaction: Interaction): Boolean
Return
whether the provided interaction exists inside this InteractionState.

interactionPositionFor

fun interactionPositionFor(interaction: Interaction): Offset?

Returns the position for a particular Interaction, if there is a position associated with the interaction.

Return
position associated with the interaction, or null if the interaction is not present in this state, or there is no associated position with the given interaction.

removeInteraction

fun removeInteraction(interaction: Interaction): Unit

Removes the provided interaction, if it is present, from this InteractionState.

Properties

value

val value: Set<Interaction>

The Set containing all Interactions present in this InteractionState. Note that this set is ordered, and the most recently added Interaction will be the last element in the set. For representing the most recent Interaction in a component, you should iterate over the set in reversed order, until you find an Interaction that you are interested in.