How to perform a haptic feedback in Jetpack Compose
Asked Answered
S

5

30

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.

Shreve answered 11/7, 2021 at 6:26 Comment(0)
H
46

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

val haptic = LocalHapticFeedback.current
Row(
    Modifier.clickable {
        haptic.performHapticFeedback(HapticFeedbackType.LongPress)
    }
)
Harleyharli answered 11/7, 2021 at 13:18 Comment(5)
HapticFeedbackType.TextHandleMove , doesn't seems to be working when performed a clickable action only HapticFeedbackType.LongPress works.Shreve
It doesn't work at allAnile
Apparently, Modifier.pointerInput() will not work if you use Modifier.clickable. I have some logic that needs to be performed in pointerInput. Any workaround for this?Gelatinize
what is context for?Waylin
It worked perfectly fine with me in the onDragStart method of the detectDragGesturesAfterLongPress modifier function.Roberts
C
29

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.

Crosswind answered 9/12, 2021 at 6:25 Comment(3)
For me, the haptic version didn't work but the view one did.Marketing
For me, the haptic version didn't work in compose preview while the view one did. However both works when running on actual app.Kine
Initially I thought the haptic version didn't work. Later I found that it's because I had put the phone on the table while performing the click action using my finger and I didn't feel the haptic on the finger. Holding the phone using the other hand however, I can feel the small haptic in both cases.Kine
M
11

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()
    }) { ... }
}

Morris answered 25/1, 2022 at 18:22 Comment(3)
This is the correct answerAnile
I found view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) worked with the shorter snippet; Modifier from the other answer appeared not to do anything for me.Marketing
For my phone adding HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING did the trick. Thank you.Dentilingual
P
5

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)
    }
}
Prizewinner answered 15/8, 2022 at 20:6 Comment(1)
This is the best answer. Be aware tho this will only work if haptic is enabled in your android settings.Disherison
P
-4

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

Plains answered 11/7, 2021 at 6:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.