The Reactive Contract
How Jindong decides when to play and which values it reads
The Reactive Contract
Jindong plays on entry by default, as with LaunchedEffect. A change to any key compiles and plays its content, cancelling the previous playback. Three rules describe which values it reads and when it fires.
Rule 1: values in content are frozen at compile time
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.
If level changes but trigger does not, the played pattern keeps the level from the last time trigger changed.
Rule 2: keys are the playback trigger
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.
A key is a value to compare, not a Boolean condition. Both false → true and true → false trigger playback. To emit haptics only on changes to a true condition, suppress entry playback and check the condition inside the content:
This call stays silent on entry even if success is already true. Without the option, a true initial value plays on entry.
The distinction matters most with continuous input. A slider value passed as a key fires a vibration on every drag step:
The same value as a parameter shapes the next playback without firing on each step:
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:
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.
Rule 3: cancel-and-restart is best effort
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.
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.
Playback ownership
Each default executor owns its current playback session. Coroutine cancellation, a key change, or leaving composition cancels that session's native effect. Starting a replacement cancels the previous session first. Cancelling an old handle later does not cancel the replacement.
JindongProvider releases its previous executor when the platform context changes and releases
the current executor when the provider leaves composition. A released executor rejects new
playback. HapticManager.release() can be followed by a new initialization.
Silent timelines wait for their logical duration without starting a native effect. Empty timelines finish immediately. Completion remains an estimate; these checks do not measure physical actuator stop time.
Choosing keys
| Value | Where it goes | Effect |
|---|---|---|
| Should fire playback when it changes | Key | Each change plays |
| Shapes the pattern, read at the next fire | Parameter in the block | Frozen until a key changes |
| Should both shape and fire | Key and read in the block | Each change plays with the new value |
Initial playback
By default, Jindong plays once on entry. This preserves existing behavior, including Jindong(Unit) and calls without keys.
Avoiding entry playback
For feedback tied to user input, set playOnInitialComposition = false. The initial key values then establish a baseline without playback:
This avoids vibrating when clickCount starts at zero and plays when the counter changes. Later key changes compile the current content and play once. Non-key values stay frozen until a key changes. Changing only the option does not compile or play again.
After Jindong leaves and re-enters composition, the initial policy applies again. Changes while it is absent are not replayed. The option uses temporary composition state and does not persist across entries.
With the option set to false, constant or absent keys never trigger playback. Conditional entry still uses the selected policy: omitted or true plays on each entry, while false waits for a later key change.
Future 2.x default
Changing the default to false is deferred to a separate breaking change for 2.x. This addition preserves both the existing function symbols and entry behavior. Use the explicit false option now when entry playback is unwanted.