Android SDK
The Pulsar Android SDK provides haptic feedback through three building blocks: Presets, PatternComposer, and RealtimeComposer. All are accessed through the main Pulsar class.
Requirements
Section titled “Requirements”- Android API 24+ (Android 7.0)
- Kotlin 1.9+
Installation
Section titled “Installation”Latest available version: 1.3.0
Add Pulsar as a Gradle dependency:
dependencies { implementation("com.swmansion:pulsar:1.3.0")}Declare the vibration permission in your 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.
Create a Pulsar instance with an Android Context:
import com.swmansion.pulsar.Pulsar
val pulsar = Pulsar(context)Presets
Section titled “Presets”A collection of ready-to-use haptic patterns accessed through pulsar.getPresets().
val presets = pulsar.getPresets()Built-in presets
Section titled “Built-in presets”| Method | Description |
|---|---|
afterglow() | A three-beat phrase that dissolves gently, ideal for soft endings or gradually quieting feedback. |
aftershock() | A firm opening that settles calmly, ideal for transitions needing a strong start and a gentle finish. |
alarm() | Relentless and urgent, best for critical errors or emergencies that require immediate attention. |
anvil() | The full weight of a massive collision, conveys sheer physical force and momentum. |
applause() | A growing wave of appreciation, ideal for celebratory moments or social approval. |
Example
val presets = pulsar.getPresets()
presets.applause()System presets
Section titled “System presets”These mirror standard haptic feedback styles shared across platforms.
| Method | Description |
|---|---|
systemImpactHeavy() | Heavy impact |
systemImpactLight() | Light impact |
systemImpactMedium() | Medium impact |
systemImpactRigid() | Rigid impact |
systemImpactSoft() | Soft impact |
Example
val presets = pulsar.getPresets()
presets.systemImpactMedium()Android-specific system presets
Section titled “Android-specific system presets”Android exposes additional system effects beyond the cross-platform set.
| Method | Description |
|---|---|
systemCalendarDate() | HapticFeedbackConstants.CALENDAR_DATE |
systemClockTick() | HapticFeedbackConstants.CLOCK_TICK |
systemConfirm() | HapticFeedbackConstants.CONFIRM |
systemContextClick() | HapticFeedbackConstants.CONTEXT_CLICK |
systemDragCrossing() | HapticFeedbackConstants.DRAG_CROSSING |
Example
val presets = pulsar.getPresets()
presets.systemPrimitiveClick()Playing by name
Section titled “Playing by name”You can play any preset by its string name using getByName(). This returns a Preset? that you call play() on.
Available names:
AfterglowAftershockAlarmAnvilApplauseAscentBalloonPopBarrage
Example
val presets = pulsar.getPresets()
// Direct callpresets.dogBark()
// By namepresets.getByName("SystemImpactMedium")?.play()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.
val composer = pulsar.getPatternComposer()Methods
Section titled “Methods”parsePattern(hapticsData)
Section titled “parsePattern(hapticsData)”Parses a PatternData object and prepares it for playback.
fun parsePattern(hapticsData: PatternData)parsePatternWithSound(hapticsData, sound)
Section titled “parsePatternWithSound(hapticsData, sound)”Parses a pattern together with a short sound played in sync with the haptics.
fun parsePatternWithSound(hapticsData: PatternData, sound: SoundData)On devices that support audio-coupled haptics, provide an .ogg whose baked haptic channels drive the vibrator for perfect, single-stream sync. Any other file — .wav/.mp3, or a bare name (which defaults to .wav) — plays the audio while the pattern’s own generated VibrationEffect fires in parallel. See SoundData for uri, volume, offset, hapticChannels, and the startMs/durationMs trim window.
play()
Section titled “play()”Plays the previously parsed pattern.
fun play()stop()
Section titled “stop()”Stops all playback.
fun stop()Example
Section titled “Example”val composer = pulsar.getPatternComposer()
val pattern = PatternData( continuousPattern = ContinuousPattern( amplitude = listOf( ValuePoint(time = 0, value = 0f), ValuePoint(time = 200, value = 1f), ValuePoint(time = 400, value = 0f), ), frequency = listOf( ValuePoint(time = 0, value = 0.3f), ValuePoint(time = 400, value = 0.8f), ) ), discretePattern = listOf( ConfigPoint(time = 0, amplitude = 1f, frequency = 0.5f), ConfigPoint(time = 100, amplitude = 0.5f, frequency = 0.5f), ))
composer.parsePattern(pattern)composer.play()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 the shared instance from pulsar.getRealtimeComposer().
val realtime = pulsar.getRealtimeComposer()An optional strategy parameter controls how continuous haptics are simulated:
val realtime = pulsar.getRealtimeComposer(strategy = RealtimeComposerStrategy.ENVELOPE)| Strategy | Description |
|---|---|
ENVELOPE | Envelope API approximation (API 36+) |
PRIMITIVE_TICK | Composition API with varying tick intervals |
PRIMITIVE_COMPLEX | Multiple primitives selected based on frequency |
ENVELOPE_WITH_DISCRETE_PRIMITIVES | Default. Envelope API for continuous events (API 36+); composition primitives for discrete events (API 33+). |
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.
fun set(amplitude: Float, frequency: Float)playDiscrete(amplitude, frequency)
Section titled “playDiscrete(amplitude, frequency)”Plays a single discrete haptic event.
fun playDiscrete(amplitude: Float, frequency: Float)stop()
Section titled “stop()”Stops the active continuous haptic.
fun stop()isActive()
Section titled “isActive()”Returns true if a continuous haptic is currently playing.
fun isActive(): BooleanExample
Section titled “Example”val realtime = pulsar.getRealtimeComposer()
// Start a continuous hapticrealtime.set(amplitude = 0.5f, frequency = 0.8f)
// Update parameters over timerealtime.set(amplitude = 1.0f, frequency = 0.3f)
// Play a one-off discrete eventrealtime.playDiscrete(amplitude = 0.7f, frequency = 0.5f)
// Stoprealtime.stop()Preset bundles
Section titled “Preset bundles”A .pulsar bundle exported from Pulsar Studio holds your own presets — each a haptic pattern with
optional synced audio and animation. pulsar-gen turns a bundle into a typed accessor, so every
preset is a real member: a renamed preset becomes a compile error rather than a silent runtime
miss.
Generate and package
Section titled “Generate and package”Apply the Gradle plugin and drop bundles into src/pulsarBundles/. On every build it generates the
typed accessor and packages the bundle into the APK assets — no manual codegen step:
// settings.gradle.kts — the plugin resolves from Maven CentralpluginManagement { repositories { mavenCentral() gradlePluginPortal() }}plugins { id("com.swmansion.pulsar.gen") version "0.1.0" // pulsar-sync:pulsar-gen-gradle-version}
pulsarBundles { packageName.set("com.example.app.bundles")}Load and play
Section titled “Load and play”val pulsar = Pulsar(context)val bundle = pulsar.loadBundle(AcmePack.descriptor, strict = true)
bundle.heartbeatV2.play()bundle.explosion.stop()strict = true checks the packaged bundle against the generated types, so a stale APK asset fails
loudly instead of quietly missing a preset. Omit it and the check is skipped.
Each PresetHandle exposes id, duration, play(), stop(), and animation — the Lottie bytes
and timing for your own animation view. Pulsar carries and time-aligns the animation; the app
renders it:
bundle.heartbeatV2.animation?.let { myLottieView.setAnimation(it.data.inputStream(), null)}Audio authored into a preset plays natively alongside the haptics — nothing extra to wire.
For an id only known at runtime, bundle.get(id) returns a handle or null. Call bundle.dispose()
to release the native patterns when you are done with a bundle.
See Android/PulsarApp for
a working screen.
Settings
Section titled “Settings”Configuration methods available directly on the Pulsar instance.
| Method | Description |
|---|---|
enableHaptics(state: Boolean) | Enable or disable all haptic feedback |
enableSound(state: Boolean) | Enable or disable audio simulation |
enableCache(state: Boolean) | Enable or disable preset caching |
clearCache() | Clear the preset cache |
preloadPresets(presetNames: List<String>) | Preload presets by name for faster playback |
stopHaptics() | Stop all currently playing haptics |
hapticSupport() | Returns the device’s CompatibilityMode |
forceHapticsSupportLevel(mode: CompatibilityMode) | Override the detected support level |
enableImpulseCompositionMode(state: Boolean) | Enable or disable VibrationEffect.Composition for impulse-only presets (enabled by default) |
Example
Section titled “Example”val pulsar = Pulsar(context)
// Preload frequently used presetspulsar.preloadPresets(listOf("Earthquake", "Success"))
// Disable haptics temporarilypulsar.enableHaptics(false)
// Check device supportval support = pulsar.hapticSupport()if (support >= CompatibilityMode.STANDARD_SUPPORT) { pulsar.getPresets().success()}PatternData
Section titled “PatternData”Describes a complete haptic pattern with discrete pulses and continuous envelope curves.
data class PatternData( val continuousPattern: ContinuousPattern, val discretePattern: List<ConfigPoint>)SoundData
Section titled “SoundData”A short sound to play in sync with a haptic pattern via parsePatternWithSound.
data class SoundData( val uri: String, // File path, file:// uri, or res/raw resource name val volume: Float = 1f, // Playback volume (0-1) val offset: Long = 0L, // Audio delay relative to haptics, in ms (fallback path) val hapticChannels: Boolean = true, val startMs: Long = 0L, // Where playback begins in the source file, in ms val durationMs: Long = 0L // How much to play from startMs, in ms (0 = to the end))The format comes from uri’s extension; when none is given the default is .wav, so a bare name like "beep" loads res/raw/beep.wav. Only an explicit .ogg with baked haptic channels uses the audio-coupled path (on supported devices); everything else plays the audio while the pattern’s own haptics fire in parallel. Set hapticChannels = false to force that fallback for an .ogg that does not carry haptic channels.
startMs and durationMs trim the clip: startMs is where playback begins in the source file and durationMs is how much of it to play (0 plays to the end). The audio is sliced to that window before playing rather than seeked at runtime, so one bundled file can back several trimmed sounds without losing sync.
ContinuousPattern
Section titled “ContinuousPattern”Represents continuous haptic curves for amplitude and frequency.
data class ContinuousPattern( val amplitude: List<ValuePoint>, val frequency: List<ValuePoint>)ValuePoint
Section titled “ValuePoint”A single point in a continuous curve.
data class ValuePoint( val time: Long, // Milliseconds from pattern start val value: Float // Normalized value (0-1))ConfigPoint
Section titled “ConfigPoint”A single discrete haptic event.
data class ConfigPoint( val time: Long, // Milliseconds from pattern start val amplitude: Float, // Intensity (0-1) val frequency: Float // Sharpness (0-1))Use discretePattern for distinct taps and impacts. Use continuousPattern envelopes to shape a sustained haptic over time.
CompatibilityMode
Section titled “CompatibilityMode”The haptic capability level of the current device, returned by pulsar.hapticSupport(). The SDK gracefully degrades based on what the device supports.
enum class CompatibilityMode { NO_SUPPORT, // No haptic support LIMITED_SUPPORT, // Timing-based waveform vibration (API 26+) STANDARD_SUPPORT, // Amplitude + timing control ADVANCED_SUPPORT, // Envelope + frequency profile (API 36+)}RealtimeComposerStrategy
Section titled “RealtimeComposerStrategy”Controls how continuous haptics are rendered on the device.
enum class RealtimeComposerStrategy { ENVELOPE, // Envelope API approximation (API 36+) PRIMITIVE_TICK, // Composition API with varying intervals PRIMITIVE_COMPLEX, // Multiple primitives based on frequency ENVELOPE_WITH_DISCRETE_PRIMITIVES, // Envelope for continuous; primitives for discrete}Preset
Section titled “Preset”The interface that all preset implementations conform to.
interface Preset { fun play()}