Kotlin Multiplatform SDK
The Pulsar Kotlin Multiplatform SDK exposes the same building blocks as the native SDKs — Presets, PatternComposer, and RealtimeComposer — from common Kotlin code. The library targets Android and iOS (iosX64, iosArm64, iosSimulatorArm64) and bridges to the native Pulsar implementation on each platform.
Requirements
Section titled “Requirements”- Kotlin 2.0+
- Android API 24+ (Android 7.0) for Android targets
- iOS 13+ for iOS targets
Installation
Section titled “Installation”Latest available version: 0.0.2
Add Pulsar KMP as a Gradle dependency in your shared module:
dependencies { implementation("com.swmansion:pulsar-kmp:0.0.2")}The library publishes the artifact as a Kotlin Multiplatform module — Gradle will resolve the right variant for each target automatically.
Android setup
Section titled “Android setup”Pulsar registers itself on Android through a ContentProvider (PulsarInitializer), so 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 target uses CoreHaptics directly — no extra setup beyond adding the dependency. When consuming the KMP library from an Xcode project, link the produced framework as you would any other Kotlin/Native binary.
Create a Pulsar instance from common code:
import com.swmansion.pulsar.kmp.Pulsar
val pulsar = Pulsar.create()Pulsar.create() resolves the platform implementation registered for the current target. On Android the factory is registered automatically by PulsarInitializer; on iOS the factory is provided by the iOS target. If you need to register a custom factory (for tests or advanced setups), use Pulsar.registerFactory(...) before the first create() call.
Presets
Section titled “Presets”A collection of ready-to-use haptic patterns accessed through pulsar.getPresets().
val presets = pulsar.getPresets()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 API.
val presets = pulsar.getPresets()
presets.hammer()presets.dogBark()presets.fanfare()Playing by name
Section titled “Playing by name”Built-in presets can also be played by passing their name to play(). The method returns true if the preset was found and dispatched, or false otherwise.
fun play(name: String): BooleangetByName(name) returns a lightweight PulsarPreset handle that can be played later, or null if the name is unknown:
fun getByName(name: String): PulsarPreset?Available names:
AfterglowAftershockAlarmAnvilApplauseAscentBalloonPopBarrage
Example
val presets = pulsar.getPresets()
presets.play("Hammer")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(name: String) | Preload a single preset by name |
preloadPresetByNames(names: List<String>) | Preload multiple presets in a batch |
enableCache(state: Boolean) | Enable or disable preset caching |
isCacheEnabled(): Boolean | 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 common code without name lookup.
| Method | Description |
|---|---|
systemImpactHeavy() | Heavy impact |
systemImpactLight() | Light impact |
systemImpactMedium() | Medium impact |
systemImpactRigid() | Rigid impact |
systemImpactSoft() | Soft impact |
Example
val presets = pulsar.getPresets()
presets.systemImpactMedium()presets.systemNotificationSuccess()Android-specific system presets
Section titled “Android-specific system presets”Android’s HapticFeedbackConstants and VibrationEffect system styles are also surfaced on the common API. They are no-ops on iOS, so you can call them from common 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.
val composer = pulsar.getPatternComposer()Methods
Section titled “Methods”parsePattern(pattern)
Section titled “parsePattern(pattern)”Parses a PatternData object and prepares it for playback.
fun parsePattern(pattern: PatternData)playPattern(pattern)
Section titled “playPattern(pattern)”Convenience method that parses and immediately plays a pattern.
fun playPattern(pattern: PatternData)play()
Section titled “play()”Plays the previously parsed pattern.
fun play()playAudioOnly()
Section titled “playAudioOnly()”Plays only the audio simulation of the parsed pattern, skipping the haptic engine. Useful for previewing patterns on simulators.
fun playAudioOnly()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.playPattern(pattern)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().
val realtime = pulsar.getRealtimeComposer()On Android, you can also request a composer that uses a specific strategy, or read/write the global default via the realtimeComposerStrategy property on Pulsar. See RealtimeComposerStrategy for available strategies.
val realtime = pulsar.getRealtimeComposer(RealtimeComposerStrategy.PRIMITIVE_COMPLEX)
pulsar.realtimeComposerStrategy = RealtimeComposerStrategy.ENVELOPEMethods
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()
realtime.set(amplitude = 0.5f, frequency = 0.8f)realtime.set(amplitude = 1.0f, frequency = 0.3f)
realtime.playDiscrete(amplitude = 0.7f, frequency = 0.5f)
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.
val haptics = pulsar.createAdaptiveHaptics(preset)AdaptivePresetConfig
Section titled “AdaptivePresetConfig”sealed class AdaptivePresetConfig { class Function(val play: (PulsarPresets) -> Unit) : AdaptivePresetConfig() class Pattern(val pattern: PatternData) : AdaptivePresetConfig()}Function— a callback that receives thePulsarPresetsinstance and triggers a native or built-in preset directly.Pattern— a customPatternDataparsed once on creation and replayed on eachplay().
AdaptivePreset
Section titled “AdaptivePreset”data class AdaptivePreset( val ios: AdaptivePresetConfig, val android: AdaptivePresetConfig,)Returns
Section titled “Returns”play()
Section titled “play()”Plays the configuration for the current platform. If the platform config is a Function, it is invoked. If it is a Pattern, it is played via the underlying pattern composer.
stop()
Section titled “stop()”Stops the active pattern. No-op when the platform config is a Function.
Example
Section titled “Example”import com.swmansion.pulsar.kmp.AdaptiveHapticsimport com.swmansion.pulsar.kmp.AdaptivePresetimport com.swmansion.pulsar.kmp.AdaptivePresetConfigimport com.swmansion.pulsar.kmp.ConfigPointimport com.swmansion.pulsar.kmp.ContinuousPatternimport com.swmansion.pulsar.kmp.PatternDataimport com.swmansion.pulsar.kmp.Pulsar
val pulsar = Pulsar.create()
val adaptivePreset = AdaptivePreset( ios = AdaptivePresetConfig.Function { presets -> presets.systemNotificationSuccess() }, android = AdaptivePresetConfig.Pattern( PatternData( continuousPattern = ContinuousPattern(amplitude = emptyList(), frequency = emptyList()), discretePattern = listOf( ConfigPoint(time = 0, amplitude = 1f, frequency = 0.5f), ConfigPoint(time = 150, amplitude = 0.6f, frequency = 0.4f), ), ), ),)
val haptics = pulsar.createAdaptiveHaptics(adaptivePreset)haptics.play()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 |
isCacheEnabled() | Returns true if preset caching is enabled |
clearCache() | Clear the preset cache |
preloadPresets(presetNames: List<String>) | Preload presets by name for faster playback |
stopHaptics() | Stop all currently playing haptics |
shutDownEngine() | 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(): CompatibilityMode | Returns the device’s CompatibilityMode |
forceHapticsSupportLevel(mode: CompatibilityMode) | (Android only) Override the detected support level. Intended for testing fallback behavior. |
enableImpulseCompositionMode(state: Boolean) | (Android only) Enable or disable impulse composition mode |
realtimeComposerStrategy (property) | (Android only) Get or set the default RealtimeComposerStrategy |
Example
Section titled “Example”val pulsar = Pulsar.create()
pulsar.preloadPresets(listOf("Hammer", "DogBark"))
if (pulsar.canPlayHaptics()) { pulsar.getPresets().play("Hammer")}
pulsar.enableHaptics(false)All shared types live in the com.swmansion.pulsar.kmp package and are usable from common code.
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>)A secondary constructor accepts the raw triplet form (List<List<List<Float>>> for continuous, List<List<Float>> for discrete) for parity with serialized pattern definitions.
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().
enum class CompatibilityMode { NO_SUPPORT, LIMITED_SUPPORT, STANDARD_SUPPORT, ADVANCED_SUPPORT,}On Android, pulsar.forceHapticsSupportLevel(mode) accepts these values to override the detected fallback path.
Example
val pulsar = Pulsar.create()
if (pulsar.hapticSupport() >= CompatibilityMode.STANDARD_SUPPORT) { 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 assign to pulsar.realtimeComposerStrategy.
enum class RealtimeComposerStrategy { ENVELOPE, PRIMITIVE_TICK, PRIMITIVE_COMPLEX, ENVELOPE_WITH_DISCRETE_PRIMITIVES,}| 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+. |
PRIMITIVE_TICK | 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. |
PRIMITIVE_COMPLEX | Similar to PRIMITIVE_TICK, but uses multiple primitives depending on the requested frequency. |
ENVELOPE_WITH_DISCRETE_PRIMITIVES | 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. |
ControlPoint
Section titled “ControlPoint”Used internally by curve interpolation and exposed for advanced custom composers.
data class ControlPoint( val intensity: Float, val sharpness: Float, val duration: Long)Custom platform factories
Section titled “Custom platform factories”For tests or hosting environments where the default initializer cannot run, register a custom factory before the first Pulsar.create() call:
import com.swmansion.pulsar.kmp.Pulsarimport com.swmansion.pulsar.kmp.PulsarPlatformFactory
Pulsar.registerFactory(myFactory)val pulsar = Pulsar.create()PulsarPlatformFactory produces a PulsarPlatformHandle, which exposes the low-level operations (presets(), patternComposer(), realtimeComposer(), lifecycle, capability checks) that the public API delegates to.