|
| 1 | +--- |
| 2 | +title: The Reactive Contract |
| 3 | +description: How Jindong decides when to play and which values it reads |
| 4 | +--- |
| 5 | + |
| 6 | +import { Callout } from 'fumadocs-ui/components/callout'; |
| 7 | + |
| 8 | +# The Reactive Contract |
| 9 | + |
| 10 | +`Jindong` behaves like `LaunchedEffect`: it plays the pattern in its content block, and a change to any key restarts it. Three rules describe exactly which values it reads and when it fires. Everything about reactive `Jindong` usage follows from them. |
| 11 | + |
| 12 | +## Rule 1: values in content are frozen at compile time |
| 13 | + |
| 14 | +When a key changes, `Jindong` compiles the content block into a pattern once and plays it. The compilation runs in a composition that produces the pattern in a single pass and is then torn down, so there is no recomposition. A state value the block reads is read once, at that compile, and does not update on its own afterward. |
| 15 | + |
| 16 | +```kotlin |
| 17 | +var level by remember { mutableStateOf(1) } |
| 18 | + |
| 19 | +// `level` is read once, when the current key last changed. |
| 20 | +Jindong(trigger) { |
| 21 | + RepeatWithIndex(level) { index -> |
| 22 | + Haptic(50.ms) |
| 23 | + } |
| 24 | +} |
| 25 | +``` |
| 26 | + |
| 27 | +If `level` changes but `trigger` does not, the played pattern keeps the `level` from the last time `trigger` changed. |
| 28 | + |
| 29 | +## Rule 2: keys are the playback trigger |
| 30 | + |
| 31 | +A key change is what fires playback. Put a value in the keys exactly when its change should play. Values that only shape the pattern belong inside the block as parameters. |
| 32 | + |
| 33 | +The distinction matters most with continuous input. A slider value passed as a key fires a vibration on every drag step: |
| 34 | + |
| 35 | +```kotlin |
| 36 | +// Fires on every value change while dragging. |
| 37 | +Jindong(sliderValue) { |
| 38 | + Haptic((sliderValue * 100).toInt().ms) |
| 39 | +} |
| 40 | +``` |
| 41 | + |
| 42 | +The same value as a parameter shapes the next playback without firing on each step: |
| 43 | + |
| 44 | +```kotlin |
| 45 | +// Reads sliderValue when `commit` changes; does not fire mid-drag. |
| 46 | +Jindong(commit) { |
| 47 | + Haptic((sliderValue * 100).toInt().ms) |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +To make a value both shape the pattern and re-fire when it changes, pass it as a key and read it inside the block: |
| 52 | + |
| 53 | +```kotlin |
| 54 | +Jindong(level) { |
| 55 | + RepeatWithIndex(level) { index -> |
| 56 | + Haptic(50.ms, HapticIntensity.Custom(1f - index * 0.1f)) |
| 57 | + } |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +<Callout type="info"> |
| 62 | +Prebuilt patterns follow the same rule. `Clip` captures its pattern at compile time, so thread the pattern through the keys to re-fire on change: `Jindong(pattern) { Clip(pattern) }`. See [Clip](/docs/api/jindong-compose/composable-dsl/clip). |
| 63 | +</Callout> |
| 64 | + |
| 65 | +## Rule 3: cancel-and-restart is best effort |
| 66 | + |
| 67 | +A key change cancels the in-flight playback before starting the new one. The manager serializes this with a state lock, so a fast sequence of key changes cancels in order and the last one wins. |
| 68 | + |
| 69 | +The cancellation itself is best effort. Neither `Vibrator.cancel()` on Android nor `CHHapticPatternPlayer.stop` on iOS reports when the hardware has actually stopped, so a few milliseconds of physical overlap between the old and new playback are possible. Patterns that change keys rapidly should not assume a clean cut between them. |
| 70 | + |
| 71 | +## Choosing keys |
| 72 | + |
| 73 | +| Value | Where it goes | Effect | |
| 74 | +|-------|---------------|--------| |
| 75 | +| Should fire playback when it changes | Key | Each change plays | |
| 76 | +| Shapes the pattern, read at the next fire | Parameter in the block | Frozen until a key changes | |
| 77 | +| Should both shape and fire | Key and read in the block | Each change plays with the new value | |
| 78 | + |
| 79 | +## See also |
| 80 | + |
| 81 | +- [Jindong API](/docs/api/jindong-compose/jindong) |
| 82 | +- [Clip](/docs/api/jindong-compose/composable-dsl/clip) |
| 83 | +- [Quick Start](/docs/guide/quick-start) |
0 commit comments