Skip to content

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.

  • Android API 24+ (Android 7.0)
  • Kotlin 1.9+

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)

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

val presets = pulsar.getPresets()
MethodDescription
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()

These mirror standard haptic feedback styles shared across platforms.

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

Example

val presets = pulsar.getPresets()
presets.systemImpactMedium()

Android exposes additional system effects beyond the cross-platform set.

MethodDescription
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()

You can play any preset by its string name using getByName(). This returns a Preset? that you call play() on.

Available names:

  • Afterglow
  • Aftershock
  • Alarm
  • Anvil
  • Applause
  • Ascent
  • BalloonPop
  • Barrage

Example

val presets = pulsar.getPresets()
// Direct call
presets.dogBark()
// By name
presets.getByName("SystemImpactMedium")?.play()

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()

Parses a PatternData object and prepares it for playback.

fun parsePattern(hapticsData: PatternData)

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.

Plays the previously parsed pattern.

fun play()

Stops all playback.

fun stop()
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()

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)
StrategyDescription
ENVELOPEEnvelope API approximation (API 36+)
PRIMITIVE_TICKComposition API with varying tick intervals
PRIMITIVE_COMPLEXMultiple primitives selected based on frequency
ENVELOPE_WITH_DISCRETE_PRIMITIVESDefault. Envelope API for continuous events (API 36+); composition primitives for discrete events (API 33+).

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)

Plays a single discrete haptic event.

fun playDiscrete(amplitude: Float, frequency: Float)

Stops the active continuous haptic.

fun stop()

Returns true if a continuous haptic is currently playing.

fun isActive(): Boolean
val realtime = pulsar.getRealtimeComposer()
// Start a continuous haptic
realtime.set(amplitude = 0.5f, frequency = 0.8f)
// Update parameters over time
realtime.set(amplitude = 1.0f, frequency = 0.3f)
// Play a one-off discrete event
realtime.playDiscrete(amplitude = 0.7f, frequency = 0.5f)
// Stop
realtime.stop()

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.

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 Central
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
app/build.gradle.kts
plugins {
id("com.swmansion.pulsar.gen") version "0.1.0" // pulsar-sync:pulsar-gen-gradle-version
}
pulsarBundles {
packageName.set("com.example.app.bundles")
}
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.


Configuration methods available directly on the Pulsar instance.

MethodDescription
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)
val pulsar = Pulsar(context)
// Preload frequently used presets
pulsar.preloadPresets(listOf("Earthquake", "Success"))
// Disable haptics temporarily
pulsar.enableHaptics(false)
// Check device support
val support = pulsar.hapticSupport()
if (support >= CompatibilityMode.STANDARD_SUPPORT) {
pulsar.getPresets().success()
}

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

data class PatternData(
val continuousPattern: ContinuousPattern,
val discretePattern: List<ConfigPoint>
)

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.

Represents continuous haptic curves for amplitude and frequency.

data class ContinuousPattern(
val amplitude: List<ValuePoint>,
val frequency: List<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)
)

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.

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+)
}

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
}

The interface that all preset implementations conform to.

interface Preset {
fun play()
}