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”set(amplitude, frequency)
Section titled “set(amplitude, frequency)”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);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();
await realtime.set(0.5, 0.8);await realtime.set(1.0, 0.3);
await realtime.playDiscrete(0.7, 0.5);
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. |