The reference manual states “never subscribe in onLoad, since there is no onUnload” and “subscriptions are unsubscribed automatically after view is deactivated”. It also documents onDeactivate and the $.unsubscribe() token pattern.
What the manual does not explain is: which events trigger deactivation (lap and auto-lap overlays do), that the Zapp output channel is also severed, what the silent failure looks like in practice, or how to diagnose it. The findings below describe the practical implications of the documented rule.
Struggling with understanding this I did some systematic testing. The findings are below
Lifecycle overview
Event
When it fires
Fires how many times
onLoad
When the uiView is first shown
Once per exercise session
onActivate
Each time the view becomes the active screen
Repeatedly — on first show, and again after any overlay or screen switch
Overlays that trigger a new onActivate: manual lap, auto-lap, and likely other system overlays.
What survives across multiple onActivate calls
Thing
Survives?
Variables declared in onLoad
Yes — persist for the full session
$.subscribe() set up in onLoad
No — severed by firmware on second onActivate
$.subscribe() set up in onActivate
Effectively yes — old one severed, new one created each time
Zapp output channel (output.X → /Zapp/{zapp_index}/Output/X)
No — also severed on second onActivate
evaluate() in main.js
Yes — unaffected, keeps running throughout
The key insight: the firmware severs all $.subscribe() calls regardless of where they were set up, but since onActivate runs again, subscriptions placed there are automatically re-created.
Recommended pattern
Place data variables in onLoad (they persist) and subscriptions in onActivate (they are re-created on each activation).
onActivate = "
// Subscriptions here — re-created on every activation
$.subscribe('/Dev/Time/Tick10hz', function() {
// render / refresh canvases
})
$.subscribe('/Activity/Move/-1/HeartRate/Current', function(v) {
// access variables declared in onLoad — they persist
if (isFinite(v) && v > 0) {
hrRecord[hrTickCount] = Math.floor(v * 60);
hrTickCount++;
}
})
"
onLoad="
// Data variables here — survive the full session
var hrRecord = new Uint8Array(3800);
var hrTickCount = 0;
// ...
"
Do not subscribe to Zapp outputs (/Zapp/{zapp_index}/Output/Name) for data that must survive lap overlays — the output channel is severed along with the subscription.
How this was verified
systemEvent() was used to log lifecycle events with sequential counters:
// In onLoad:
loadCount++;
systemEvent('onLoad #' + loadCount); // should always be #1
// In onActivate:
activateCount++;
systemEvent('onActivate #' + activateCount); // increments on each overlay
// In subscription callback:
systemEvent('hrTick=' + hrTickCount); // stops if subscription was severed
After a lap overlay the log showed onActivate #2 followed by no further hrTick entries — confirming the subscription was severed. Moving the subscription to onActivate resolved it: hrTick entries continued uninterrupted through all subsequent laps.
Hopes this helps provide some additional information around this topic.