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.
Requirements
Section titled “Requirements”- Flutter 3.3+
- Dart 3.11+
- Android API 24+ (Android 7.0)
- iOS 13+
Installation
Section titled “Installation”Latest available version: 0.0.3
Add Pulsar to your pubspec.yaml:
dependencies: pulsar_haptics: ^0.0.3Or install via the Flutter CLI:
flutter pub add pulsar_hapticsAndroid setup
Section titled “Android setup”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.
iOS setup
Section titled “iOS setup”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.
Presets
Section titled “Presets”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.
Playing by direct method
Section titled “Playing by direct method”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();Playing by name
Section titled “Playing by name”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:
afterglowaftershockalarmanvilapplauseascentballoonPopbarrage
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.
Preloading and caching
Section titled “Preloading and caching”PulsarPresets exposes its own preload/cache controls in addition to the equivalents on the Pulsar instance.
| Method | Description |
|---|---|
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 |
System presets
Section titled “System presets”Cross-platform system feedback styles are exposed as direct methods so you can call them from Dart without name lookup.
| Method | Description |
|---|---|
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-specific system presets
Section titled “Android-specific system presets”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.
| Method | Description |
|---|---|
systemEffectClick() | VibrationEffect.EFFECT_CLICK |
systemEffectDoubleClick() | VibrationEffect.EFFECT_DOUBLE_CLICK |
systemEffectTick() | VibrationEffect.EFFECT_TICK |
systemEffectHeavyClick() | VibrationEffect.EFFECT_HEAVY_CLICK |
systemLongPress() | HapticFeedbackConstants.LONG_PRESS |
PatternComposer
Section titled “PatternComposer”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.
Methods
Section titled “Methods”parsePattern(pattern)
Section titled “parsePattern(pattern)”Parses a PatternData object and prepares it for playback.
Future<void> parsePattern(PatternData data);playPattern(pattern)
Section titled “playPattern(pattern)”Convenience method that parses and immediately plays a pattern.
Future<void> playPattern(PatternData data);play()
Section titled “play()”Plays the previously parsed pattern. Throws StateError if no pattern has been parsed yet.
Future<void> play();playAudioOnly()
Section titled “playAudioOnly()”Plays only the audio simulation of the parsed pattern, skipping the haptic engine. Useful for previewing patterns on simulators.
Future<void> playAudioOnly();stop()
Section titled “stop()”Stops all playback for this composer.
Future<void> stop();dispose()
Section titled “dispose()”Releases the native composer handle held by this instance. After calling dispose, the composer can no longer be played.
Future<void> dispose();Example
Section titled “Example”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]],);RealtimeComposer
Section titled “RealtimeComposer”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);Methods
Section titled “Methods”start()
Section titled “start()”Starts the realtime composer so that subsequent set(...) calls play. set(...) requires start() first — calls made before start() or after stop() are silent no-ops. After stop(), call start() again before the next set(...). Routes through the active strategy on the same path as the other methods.
Future<void> start();set(amplitude, frequency, {startIfNeeded})
Section titled “set(amplitude, frequency, {startIfNeeded})”Updates the ongoing haptic with new amplitude and frequency values. Values should be in the 0–1 range. Has no effect until start() has been called; becomes a no-op again after stop(). Pass startIfNeeded: true to auto-start the composer when it is not already active — defaults to false, preserving the no-op-when-inactive behavior.
Future<void> set(double amplitude, double frequency, {bool startIfNeeded = false});playDiscrete([amplitude, frequency])
Section titled “playDiscrete([amplitude, 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]);stop()
Section titled “stop()”Stops the active continuous haptic.
Future<void> stop();isActive()
Section titled “isActive()”Returns true if a continuous haptic is currently playing.
Future<bool> isActive();Example
Section titled “Example”final realtime = pulsar.getRealtimeComposer();
// Begin a continuous haptic — required before set(...) will playawait realtime.start();
// Update parameters over time as the gesture progressesawait realtime.set(0.5, 0.8);await realtime.set(1.0, 0.3);
await realtime.playDiscrete(0.7, 0.5);
// End the continuous haptic. After this the composer is inactive// again — call start() before the next set(...).await realtime.stop();AdaptiveHaptics
Section titled “AdaptiveHaptics”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.
AdaptivePresetConfig
Section titled “AdaptivePresetConfig”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 onplay(). Use it to trigger a native or built-in preset.AdaptivePresetPattern— a customPatternDataparsed once on creation and replayed on eachplay().
AdaptivePreset
Section titled “AdaptivePreset”class AdaptivePreset { const AdaptivePreset({required this.ios, required this.android});
final AdaptivePresetConfig ios; final AdaptivePresetConfig android;}Methods
Section titled “Methods”play()
Section titled “play()”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();stop()
Section titled “stop()”Stops the active pattern. No-op when the platform config is a callback.
Future<void> stop();dispose()
Section titled “dispose()”Releases the underlying native resources. Call when the adaptive instance is no longer needed.
Future<void> dispose();Example
Section titled “Example”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();Settings
Section titled “Settings”Configuration methods available directly on the Pulsar instance. All return a Future.
| Method | Description |
|---|---|
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 |
Example
Section titled “Example”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.
PatternData
Section titled “PatternData”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.
ContinuousPattern
Section titled “ContinuousPattern”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;}ValuePoint
Section titled “ValuePoint”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)}DiscretePoint
Section titled “DiscretePoint”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.
HapticSupport
Section titled “HapticSupport”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();}RealtimeComposerStrategy
Section titled “RealtimeComposerStrategy”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,}| Value | Description |
|---|---|
envelope | Approximation based on the Envelope API. Allows control over amplitude and frequency, but the signal oscillates and can be unstable. Available on Android API 36+. |
primitiveTick | Approximation 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. |
primitiveComplex | Similar to primitiveTick, but uses multiple primitives depending on the requested frequency. |
envelopeWithDiscretePrimitives | Default. 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. |