Skip to content

Flutter SDK

The Pulsar Flutter SDK exposes the same building blocks as the native SDKs — Presets, PatternComposer, and RealtimeComposer — through a Dart-friendly API. The plugin bridges to the native Pulsar implementation on Android and iOS, so you get identical behavior on both platforms.

  • Flutter 3.3+
  • Dart 3.11+
  • Android API 24+ (Android 7.0)
  • iOS 13+

Latest available version: 0.0.3

Add Pulsar to your pubspec.yaml:

dependencies:
pulsar_haptics: ^0.0.3

Or install via the Flutter CLI:

Terminal window
flutter pub add pulsar_haptics

Pulsar registers itself on Android automatically through the Flutter plugin lifecycle — no manual wiring is required.

Declare the vibration permission in your Android app’s AndroidManifest.xml:

<manifest ...>
<uses-permission android:name="android.permission.VIBRATE" />
<application ...>
...
</application>
</manifest>

Without android.permission.VIBRATE, Android will block vibration playback. Pulsar logs a warning and skips the vibration instead of crashing.

The iOS plugin uses CoreHaptics directly — no extra setup beyond running pod install after adding the dependency.

Create a Pulsar instance and reuse it throughout your app:

import 'package:pulsar_haptics/pulsar.dart';
final pulsar = Pulsar();

All Pulsar methods are asynchronous and return a Future. Await them when you need to chain calls or know when playback has been dispatched.


A collection of ready-to-use haptic patterns accessed through pulsar.getPresets() (or the pulsar.presets shorthand).

final presets = pulsar.getPresets();

The PulsarPresets instance is created lazily on first access and cached for the lifetime of the Pulsar instance.

Each built-in preset is exposed as a direct method on the PulsarPresets instance, mirroring the React Native and KMP APIs.

final presets = pulsar.getPresets();
await presets.hammer();
await presets.dogBark();
await presets.fanfare();

Built-in presets can also be played by passing their name to play().

Future<void> play(String name);

getByName(name) returns a lightweight PulsarPreset handle that can be played later, or null if the name is unknown:

Future<PulsarPreset?> getByName(String name);

Available names:

  • afterglow
  • aftershock
  • alarm
  • anvil
  • applause
  • ascent
  • balloonPop
  • barrage

Example

final presets = pulsar.getPresets();
await presets.play('hammer');
await presets.play('dogBark');

For per-preset descriptions, see the iOS and Android reference pages — the underlying patterns are shared.

PulsarPresets exposes its own preload/cache controls in addition to the equivalents on the Pulsar instance.

MethodDescription
preloadPresetByName(String name)Preload a single preset by name
preloadPresetByNames(List<String> names)Preload multiple presets in a batch
enableCache(bool state)Enable or disable preset caching
isCacheEnabled()Returns true if caching is currently enabled
resetCache()Drop all cached preset data

Cross-platform system feedback styles are exposed as direct methods so you can call them from Dart without name lookup.

MethodDescription
systemImpactHeavy()Heavy impact
systemImpactLight()Light impact
systemImpactMedium()Medium impact
systemImpactRigid()Rigid impact
systemImpactSoft()Soft impact

Example

final presets = pulsar.getPresets();
await presets.systemImpactMedium();
await presets.systemNotificationSuccess();

Android’s HapticFeedbackConstants and VibrationEffect system styles are also surfaced on the Dart API. They are no-ops on iOS, so you can call them from shared code — but they only produce feedback on Android.

MethodDescription
systemEffectClick()VibrationEffect.EFFECT_CLICK
systemEffectDoubleClick()VibrationEffect.EFFECT_DOUBLE_CLICK
systemEffectTick()VibrationEffect.EFFECT_TICK
systemEffectHeavyClick()VibrationEffect.EFFECT_HEAVY_CLICK
systemLongPress()HapticFeedbackConstants.LONG_PRESS

Composes and plays custom haptic patterns. Get a new instance from pulsar.getPatternComposer(). Each call returns a fresh composer, so you can manage multiple patterns independently.

final composer = pulsar.getPatternComposer();

Pattern composers hold native resources. Call dispose() when you no longer need a composer to release the underlying handle.

Parses a PatternData object and prepares it for playback.

Future<void> parsePattern(PatternData data);

Convenience method that parses and immediately plays a pattern.

Future<void> playPattern(PatternData data);

Plays the previously parsed pattern. Throws StateError if no pattern has been parsed yet.

Future<void> play();

Plays only the audio simulation of the parsed pattern, skipping the haptic engine. Useful for previewing patterns on simulators.

Future<void> playAudioOnly();

Stops all playback for this composer.

Future<void> stop();

Releases the native composer handle held by this instance. After calling dispose, the composer can no longer be played.

Future<void> dispose();
final composer = pulsar.getPatternComposer();
final pattern = PatternData(
continuousPattern: ContinuousPattern(
amplitude: const [
ValuePoint(time: 0, value: 0),
ValuePoint(time: 200, value: 1),
ValuePoint(time: 400, value: 0),
],
frequency: const [
ValuePoint(time: 0, value: 0.3),
ValuePoint(time: 400, value: 0.8),
],
),
discretePattern: const [
DiscretePoint(time: 0, amplitude: 1, frequency: 0.5),
DiscretePoint(time: 100, amplitude: 0.5, frequency: 0.5),
],
);
await composer.playPattern(pattern);

PatternData.fromArrays is a convenience constructor that accepts the raw triplet form used by the JSON pattern format:

final pattern = PatternData.fromArrays(
amplitude: [[0, 0], [200, 1], [400, 0]],
frequency: [[0, 0.3], [400, 0.8]],
discrete: [[0, 1, 0.5], [100, 0.5, 0.5]],
);

Provides real-time haptic control with live amplitude and frequency modulation. Useful for gesture-driven or continuously evolving haptic experiences. Get a composer from pulsar.getRealtimeComposer().

final realtime = pulsar.getRealtimeComposer();

The Pulsar instance keeps a single realtime composer alive. Passing a new strategy rebuilds it with that strategy and updates the active default — see RealtimeComposerStrategy.

final realtime = pulsar.getRealtimeComposer(
strategy: RealtimeComposerStrategy.primitiveComplex,
);
await pulsar.setRealtimeComposerStrategy(RealtimeComposerStrategy.envelope);

Updates the ongoing haptic with new amplitude and frequency values. Automatically starts playback if it is not already active. Values should be in the 0–1 range.

Future<void> set(double amplitude, double frequency);

Plays a single discrete haptic event. Defaults match iOS native (amplitude = 1.0, frequency = 0.5).

Future<void> playDiscrete([double amplitude = 1.0, double frequency = 0.5]);

Stops the active continuous haptic.

Future<void> stop();

Returns true if a continuous haptic is currently playing.

Future<bool> isActive();
final realtime = pulsar.getRealtimeComposer();
await realtime.set(0.5, 0.8);
await realtime.set(1.0, 0.3);
await realtime.playDiscrete(0.7, 0.5);
await realtime.stop();

Plays haptics from a cross-platform AdaptivePreset. The correct configuration is selected for the current platform automatically. Each platform can provide either a custom PatternData or a callback that triggers a built-in or system preset directly.

final haptics = await pulsar.createAdaptiveHaptics(preset);

createAdaptiveHaptics is asynchronous because pattern-based configs are pre-parsed when the instance is created, so play() can fire without delay. Call dispose() when you no longer need the instance.

sealed class AdaptivePresetConfig {}
final class AdaptivePresetCallback extends AdaptivePresetConfig {
const AdaptivePresetCallback(this.play);
final Future<void> Function() play;
}
final class AdaptivePresetPattern extends AdaptivePresetConfig {
const AdaptivePresetPattern(this.pattern);
final PatternData pattern;
}
  • AdaptivePresetCallback — a callback invoked on play(). Use it to trigger a native or built-in preset.
  • AdaptivePresetPattern — a custom PatternData parsed once on creation and replayed on each play().
class AdaptivePreset {
const AdaptivePreset({required this.ios, required this.android});
final AdaptivePresetConfig ios;
final AdaptivePresetConfig android;
}

Plays the configuration for the current platform. If the platform config is a callback, it is invoked. If it is a pattern, it is played via the underlying pattern composer.

Future<void> play();

Stops the active pattern. No-op when the platform config is a callback.

Future<void> stop();

Releases the underlying native resources. Call when the adaptive instance is no longer needed.

Future<void> dispose();
import 'package:pulsar_haptics/pulsar.dart';
final pulsar = Pulsar();
final adaptivePreset = AdaptivePreset(
ios: AdaptivePresetCallback(() => pulsar.presets.systemNotificationSuccess()),
android: AdaptivePresetPattern(
PatternData(
continuousPattern: const ContinuousPattern(
amplitude: [],
frequency: [],
),
discretePattern: const [
DiscretePoint(time: 0, amplitude: 1, frequency: 0.5),
DiscretePoint(time: 150, amplitude: 0.6, frequency: 0.4),
],
),
),
);
final haptics = await pulsar.createAdaptiveHaptics(adaptivePreset);
await haptics.play();

Configuration methods available directly on the Pulsar instance. All return a Future.

MethodDescription
enableHaptics(bool state)Enable or disable all haptic feedback
enableSound(bool state)Enable or disable audio simulation
enableCache(bool state)Enable or disable preset caching
isCacheEnabled()Returns true if preset caching is enabled
clearCache()Clear the preset cache
preloadPreset(String name)Preload a single preset
preloadPresets(List<String> names)Preload presets by name for faster playback
stopHaptics()Stop all currently playing haptics
shutDownEngine()(iOS only) Tear down the underlying haptic engine
isHapticsEnabled()Returns true if haptics are currently enabled
isHapticsSupported()Returns true if the device supports haptics
canPlayHaptics()Returns true when haptics can play right now
hapticSupport()Returns the device’s HapticSupport
forceHapticsSupportLevel(HapticSupport level)(Android only) Override the detected support level. Intended for testing fallback behavior.
enableImpulseCompositionMode(bool state)(Android only) Enable or disable impulse composition mode
setRealtimeComposerStrategy(RealtimeComposerStrategy s)(Android only) Set the default RealtimeComposerStrategy
final pulsar = Pulsar();
await pulsar.preloadPresets(['hammer', 'dogBark']);
if (await pulsar.canPlayHaptics()) {
await pulsar.getPresets().play('hammer');
}
await pulsar.enableHaptics(false);

All public types are re-exported from package:pulsar_haptics/pulsar.dart.

Describes a complete haptic pattern with discrete pulses and continuous envelope curves.

class PatternData {
const PatternData({
required this.continuousPattern,
required this.discretePattern,
});
final ContinuousPattern continuousPattern;
final List<DiscretePoint> discretePattern;
}

The PatternData.fromArrays factory accepts the raw triplet form (List<List<double>>) for parity with serialized pattern definitions.

Represents continuous haptic curves for amplitude and frequency.

class ContinuousPattern {
const ContinuousPattern({required this.amplitude, required this.frequency});
final List<ValuePoint> amplitude;
final List<ValuePoint> frequency;
}

A single point in a continuous curve.

class ValuePoint {
const ValuePoint({required this.time, required this.value});
final double time; // Milliseconds from pattern start
final double value; // Normalized value (0–1)
}

A single discrete haptic event.

class DiscretePoint {
const DiscretePoint({
required this.time,
required this.amplitude,
required this.frequency,
});
final double time; // Milliseconds from pattern start
final double amplitude; // Intensity (0–1)
final double frequency; // Sharpness (0–1)
}

Use discretePattern for distinct taps and impacts. Use continuousPattern envelopes to shape a sustained haptic over time.

The haptic capability level of the current device, returned by pulsar.hapticSupport().

enum HapticSupport {
noSupport,
limitedSupport,
standardSupport,
advancedSupport,
}

Ordinal values match the native CompatibilityMode enum. On Android, pulsar.forceHapticsSupportLevel(level) accepts these values to override the detected fallback path.

Example

final pulsar = Pulsar();
if ((await pulsar.hapticSupport()).index >= HapticSupport.standardSupport.index) {
await pulsar.getPresets().dogBark();
}

Android-only. Controls how RealtimeComposer simulates continuous haptics, since Android has no native continuous haptic API. Pass one of these values to pulsar.getRealtimeComposer(strategy: ...) or pulsar.setRealtimeComposerStrategy(...).

enum RealtimeComposerStrategy {
envelope,
primitiveTick,
primitiveComplex,
envelopeWithDiscretePrimitives,
}
ValueDescription
envelopeApproximation based on the Envelope API. Allows control over amplitude and frequency, but the signal oscillates and can be unstable. Available on Android API 36+.
primitiveTickApproximation using the Composition API TICK primitive at varying intervals. Amplitude is controllable; frequency is simulated by the timing between ticks. Signal is discrete rather than continuous.
primitiveComplexSimilar to primitiveTick, but uses multiple primitives depending on the requested frequency.
envelopeWithDiscretePrimitivesDefault. Hybrid strategy. Uses the Envelope API for continuous events (API 36+) and composition primitives for discrete events (API 33+). Best of both worlds when both event types are used.