How to perform a haptic feedback in Jetpack Compose

Viewed 2682

Using jetpack compose, for a clickevent how to perform haptic feedback. I am new to jetpack compose. This is what i tried -

val hapticFeedback = LocalHapticFeedback

@Composable
fun Tab() {
    Row() {
        Icon(imageVector = icon, contentDescription = text)
        if (selected) {
            // i tried both the following ways, none are working. 
            hapticFeedback.current.performHapticFeedback(
                HapticFeedbackType(10)
            )
            hapticFeedback.current.performHapticFeedback(HapticFeedbackType.TextHandleMove)
....
            Spacer(Modifier.width(12.dp))
            Text(text.uppercase(Locale.getDefault()))
        }
    }
}

I am able to see the text when it is getting selected, but not getting a subtle vibrating feedback.

5 Answers

In version rc-01 of Compose you can use only two types of Haptic Feedback: HapticFeedbackType.LongPress or HapticFeedbackType.TextHandleMove.

val haptic = LocalHapticFeedback.current
val context = LocalContext.current
Row(
    Modifier.clickable {
        haptic.performHapticFeedback(HapticFeedbackType.LongPress)
    }
)

Currently (Compose UI 1.1.0-beta03) only LongPress and TextHandleMove are supported via

val haptic = LocalHapticFeedback.current
Button(onClick = {
    haptic.performHapticFeedback(HapticFeedbackType.LongPress)
}) { ... }

as @nglauber answer said.

I guess it is because of the multi-platform support for Compose Desktop.

There's another way, however, if you are on Android and you do not need Compose Desktop compatibility, it's fairly easy to use it:

val view = LocalView.current
Button(onClick = {
    view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
}) { ... }

The HapticFeedbackConstants class has a lot of constants.

On my phone (and other devices I've tested on) neither LongPress nor TextHandleMove makes the phone vibrate.

We worked around this before we moved to Compose like this:

import android.content.Context
import android.view.HapticFeedbackConstants
import android.view.View
import android.view.accessibility.AccessibilityManager

fun View.vibrate() = reallyPerformHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
fun View.vibrateStrong() = reallyPerformHapticFeedback(HapticFeedbackConstants.LONG_PRESS)

private fun View.reallyPerformHapticFeedback(feedbackConstant: Int) {
    if (context.isTouchExplorationEnabled()) {
        // Don't mess with a blind person's vibrations
        return
    }
    // Either this needs to be set to true, or android:hapticFeedbackEnabled="true" needs to be set in XML
    isHapticFeedbackEnabled = true

    // Most of the constants are off by default: for example, clicking on a button doesn't cause the phone to vibrate anymore
    // if we still want to access this vibration, we'll have to ignore the global settings on that.
    performHapticFeedback(feedbackConstant, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
}

private fun Context.isTouchExplorationEnabled(): Boolean {
    // can be null during unit tests
    val accessibilityManager = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager?
    return accessibilityManager?.isTouchExplorationEnabled ?: false
}

For now we still have to use this code and access it from Compose like in Daniele Segato's answer:

@Composable
fun VibratingButton() {
    val view = LocalView.current
    Button(onClick = {
        view.vibrate()
    }) { ... }
}

Add the feedback logic inside the Modifier.clickable {...} applied to the Row

For me, this worked well when the user presses down the button. Using Compose 1.2.0-beta01

val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()

val hapticFeedback = LocalHapticFeedback.current
LaunchedEffect(key1 = isPressed) {
    if (isPressed) {
        hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
    }
}
Related