Jindong: Introducing Declarative Haptics in Compose Multiplatform
This is the speaker deck for Jindong: Introducing Declarative Haptics in Compose Multiplatform, presented at COSCUP X UbuCon Asia 2026 (Taipei, Taiwan).
Korea’s #1 finance app Community Driven Developer — GDG Korea Android, KUG Seoul, CodeRabbit User Groups Korea Open source Lover, mostly Kotlin Multiplatform (Jindong, Cloudy) and React Native (react-native-nitro-{device-info | cookies}) ❤ Guitar and the gym You can see speaker deck via this link 2026 COSCUP × UBUCON ASIA, TAIWAN 02
Describes what to achieve. • Explicitly defines every step required to complete the task • Includes operations such as data assignment, calculations, loops and conditionals • Abstracts the way a computer executes instructions • Abstracts the logical structure required to complete the task • Expresses conceptual relationships through functions and types • Focuses on business rules, data relationships and state transitions 2026 COSCUP × UBUCON ASIA, TAIWAN 13
DroidKaigi(Android Conference, Japan), I saw a project that represented sound using Composable functions. • If sound can be represented this way, could vibration be represented in the same way? 2026 COSCUP × UBUCON ASIA, TAIWAN 15
whether your idea already exists • There were already several haptics libraries for Android and Compose Multiplatform, but none were built on Compose Runtime or offered a declarative API. • (Optional) Find a good collaborator • Working alone can make the API design and project vision too one-sided. • A good collaborator can bring a different perspective and help keep the project balanced. 2026 COSCUP × UBUCON ASIA, TAIWAN 16
announcing the first stable release on Slack • Developers wanted a pure Kotlin DSL, rather than only a Compose Runtime–based API • Reconsidered the original design • Compose Runtime was initially chosen to reduce the learning curve • However, a Kotlin DSL could provide a similarly simple and expressive API • Restructured the library • Made the Kotlin-based API the core implementation • Expanded support to developers who do not use Compose or Compose Multiplatform 2026 COSCUP × UBUCON ASIA, TAIWAN 17
children: MutableList<HapticElement> fun collectEvents(startTimeMs: Long): List<ScheduledHapticEvent> fun totalDurationMs(startTimeMs: Long): Long } 2026 COSCUP × UBUCON ASIA, TAIWAN • children • Child nodes forming haptic pattern • collectEvents • Flattens the trees into a list the platform API can consume • totalDurationMs • Answers “how much time has passed once I’m done” • Parents use it to schedule the next sibling’s start 25
tracking Composition Remembers what was emitted, and where • Tracks which composables emitted which nodes, and in what order • On re-run, diffs against the previous state — computes the minimal change • The node type is generic. The runtime never names it Diff: what changed since last time Applier The only part that touches your tree Node Tree 2026 COSCUP × UBUCON ASIA, TAIWAN 32
override fun insertTopDown(index: Int, instance: HapticElement) { current.children.add(index, instance) } override fun remove(index: Int, count: Int) { current.children.subList(index, index + count).clear() } • The runtime decides what changes — the Applier decides how • Insert, remove, move — every one touches only children • current - where the runtime is in the tree, tracked by AbstractApplier • Compose UI has one too — it inserts LayoutNode instead override fun move(from: Int, to: Int, count: Int) { if (from == to) return val items = current.children.subList(from, from + count).toList() current.children.subList(from, from + count).clear() current.children.addAll(to, items) } } 2026 COSCUP × UBUCON ASIA, TAIWAN 33
• Core defines the tree — no Compose anywhere • The Applier lets Compose Runtime build that same tree • Both DSLs end up at the same event list HapticElement Tree collectEvents→ List< ScheduledHapticEvent> HapticExecutor Haptic Feedback 2026 COSCUP × UBUCON ASIA, TAIWAN 36