Flutter package
haptic_kit
Haptic feedback, vibration and animated widgets for Flutter — the right haptic at the right moment.
Patterns & haptics API
The HapticPattern builder, predefined OS effects, ready-made vibration patterns, waveforms and typed errors.
source: README.mdCore Haptics
HapticPattern builder
HapticPattern is a fluent builder for Core Haptics patterns. Chain transient taps, pauses and continuous events, each with a per-event intensity and sharpness in the range 0.0–1.0, then call play().
await HapticPattern.builder()
.tap(intensity: 0.4, sharpness: 0.6)
.pause(const Duration(milliseconds: 80))
.tap(intensity: 1.0, sharpness: 0.9)
.continuous(
duration: const Duration(milliseconds: 250),
intensity: 0.7,
sharpness: 0.3,
)
.play();factory HapticPattern.builder() = HapticPattern._;
HapticPattern tap({
required double intensity,
required double sharpness,
})
HapticPattern continuous({
required Duration duration,
required double intensity,
required double sharpness,
})
HapticPattern pause(Duration duration)
HapticPattern addEvent(HapticEvent event)
int get length // Number of events queued so far.
List<HapticEvent> get events // Snapshot of the current event list.
Future<void> play()HapticEvent
const HapticEvent({
required this.intensity,
required this.sharpness,
this.duration = Duration.zero,
this.relativeTime = Duration.zero,
})| Field | Range / type | Meaning |
|---|---|---|
intensity | double 0.0–1.0 | Overall strength of the event. |
sharpness | double 0.0–1.0 | Perceptual 'sharpness' of the event (iOS only; ignored on Android, which has no equivalent). |
duration | Duration, default zero | If non-zero, the event is continuous; otherwise it is treated as a single tap. |
relativeTime | Duration, default zero | Offset from pattern start at which this event begins. |
Platform translation & graceful degradation
On iOS (iPhone 8+) a pattern renders as a CHHapticPattern with hapticTransient / hapticContinuous events. On Android (API 26+) intensity is mapped to amplitude and sharpness is ignored (no perceptual analogue). On older devices, play() throws UnsupportedHapticException — guard with HapticCapabilities.query() if you need to fall back. An empty builder throws InvalidVibrationArgumentException, as do continuous / pause durations of 0 ms or less.
OS effects
Predefined effects
PredefinedEffect maps to VibrationEffect.EFFECT_* constants on Android API 29+, and to the closest HapticFeedback style on iOS and older Android.
enum PredefinedEffect {
tick, // Android: EFFECT_TICK
click, // Android: EFFECT_CLICK
doubleClick, // Android: EFFECT_DOUBLE_CLICK
heavyClick, // Android: EFFECT_HEAVY_CLICK
}await Vibration.playPredefined(PredefinedEffect.doubleClick);Presets
VibrationPatterns
Ready-made patterns you can drop straight into a UI callback. Each entry returns a Future so playback can be awaited.
| Preset | Signature | What it does | Built on |
|---|---|---|---|
heartbeat | static Future<void> heartbeat() | Two short pulses mimicking a heartbeat. | waveform, amplitudes [0, 200, 0, 200, 0] |
notification | static Future<void> notification() | Three escalating pulses — typical incoming-notification pattern. | waveform, amplitudes [0, 120, 0, 180, 0, 255] |
alarm | static Future<void> alarm({bool repeat = true}) | Long ringing pattern — alarms / incoming calls. Loops by default. | waveform, repeat: repeat ? 0 : -1 |
tick | static Future<void> tick() | Single very short tick — scrub / scroll feedback a hair stronger than selection. | vibrate(duration: 10 ms, amplitude: 80) |
doubleTap | static Future<void> doubleTap() | Two quick, equal taps — e.g. a double-tap acknowledgement. | HapticPattern: 2× tap(0.8 / 0.7), 90 ms pause |
success | static Future<void> success() | Confirmation: light tap → strong tap. | HapticPattern: tap(0.5 / 0.5) → 80 ms → tap(1.0 / 0.8) |
failure | static Future<void> failure() | Failure / cancel: two strong taps. | HapticPattern: 2× tap(1.0 / 0.9), 120 ms pause |
chargeUp | static Future<void> chargeUp({Duration duration = const Duration(milliseconds: 600)}) | Long ramp from soft to strong — 'charging up' UI moments. | HapticPattern continuous, intensity: 1.0, sharpness: 0.2 |
Vibration
Custom waveforms
For raw control over duration and amplitude, drive the vibrator directly. timings is a list of off/on durations starting with an off period (Android createWaveform convention); amplitudes, if provided, must have the same length and use values in [0, 255] (0 = a pause). Set repeat to the index in timings to loop from, or -1 for no loop.
const int kMaxAmplitude = 255;
const int kMinAmplitude = 1;
static Future<void> vibrate({
required Duration duration,
int? amplitude,
})
static Future<void> vibrateWaveform({
required List<Duration> timings,
List<int>? amplitudes,
int repeat = -1,
})
static Future<void> playPredefined(PredefinedEffect effect)
static Future<void> cancel()// Three pulses with growing amplitude
await Vibration.vibrateWaveform(
timings: const [
Duration.zero,
Duration(milliseconds: 100),
Duration(milliseconds: 100),
Duration(milliseconds: 100),
Duration(milliseconds: 100),
Duration(milliseconds: 100),
],
amplitudes: const [0, 80, 0, 160, 0, 255],
);vibrate throws InvalidVibrationArgumentException when the duration is zero/negative or the amplitude is outside [1, 255]; pass null for the device default, and values are silently clamped on devices without amplitude control. On iOS without Core Haptics, a waveform falls back to a sequence of system vibrations approximating timings.
Detection
HapticCapabilities
Query the device once with HapticCapabilities.query() and cache the result — the values do not change at runtime. Use it to pick the richest effect the hardware can actually render.
static Future<HapticCapabilities> query()| Field | Type | Meaning |
|---|---|---|
hasVibrator | bool | Device has any kind of vibrator hardware. |
hasAmplitudeControl | bool | Device can render variable-amplitude waveforms. Android: Vibrator.hasAmplitudeControl() (API 26+); iOS: true when Core Haptics is available. |
supportsCustomPatterns | bool | Custom HapticPatterns with intensity + sharpness. Android: API 26+ with amplitude control; iOS: Core Haptics devices (iPhone 8+). |
supportsPredefinedEffects | bool | VibrationEffect.createPredefined (Android API 29+). Always false on iOS — predefined calls are translated to the closest UIImpactFeedbackGenerator style instead. |
supportsImpactFeedback | bool | UIImpactFeedbackGenerator-style taps. iOS: true on iOS 10+; Android: true on API 23+ via the lightweight HapticFeedbackConstants path, even without an amplitude-controlled vibrator. |
final caps = await HapticCapabilities.query();
if (caps.supportsCustomPatterns) {
await VibrationPatterns.success();
} else {
await Haptics.notification(HapticNotificationStyle.success);
}Errors
Typed error handling
All public APIs throw subclasses of VibrationException. The base class is sealed, so an exhaustive switch over the three concrete types is possible.
sealed class VibrationException implements Exception {
const VibrationException(this.message);
final String message;
}
class UnsupportedHapticException extends VibrationException {
const UnsupportedHapticException(super.message);
}
class InvalidVibrationArgumentException extends VibrationException {
const InvalidVibrationArgumentException(super.message);
}
class PlatformVibrationException extends VibrationException {
const PlatformVibrationException(super.message, {this.code});
final String? code;
}| Exception | Thrown when |
|---|---|
InvalidVibrationArgumentException | A parameter is out of range (negative duration, amplitude > 255, mismatched lists, …). |
UnsupportedHapticException | The device cannot render the requested capability. |
PlatformVibrationException | The native side returned an error or the plugin is not registered. |
Let's make something together.
If you have any questions about a new project or other inquiries, feel free to contact us. We will get back to you as soon as possible.
