Survey Lifecycle Events
Every platform emits a set of events as a survey moves through its lifecycle — see each
platform’s own Events page for exact names and payload shapes, since those diverge (web’s
survey:step vs. the natives’ survey:question_answered, object vs. bare-string payloads,
and so on — Platform Parity has the full table). This page covers the
design that’s shared across all of them.
Lifecycle events don’t re-trigger themselves
Internally, each SDK tracks its own lifecycle events (survey shown/dismissed/closed,
question answered, survey completed) the same way it tracks any custom event — including
adding them to user.eventHistory. But they’re excluded from trigger candidacy: without
that exclusion, a wildcard survey (one with no triggerEventNames, which matches any event)
would match its own survey_dismissed and immediately re-show itself. This exclusion only
applies at the trigger-candidacy gate — if you deliberately write an EVENT_FREQUENCY
condition against survey_completed in a Stage 2 rule, it still sees those occurrences
normally.
on() returns a handle, not a promise to call off() correctly
on(event, callback) returns a Registration; call .cancel() on it to unsubscribe. This
exists because callback-by-reference matching doesn’t work uniformly — Swift closures aren’t
Equatable, so there’s no way to match “the same callback” by identity on iOS the way you can
in JS. A returned registration, keyed by an internal id rather than the callback reference
itself, works identically everywhere and makes cancel() idempotent: calling it twice, or
after the listener already fired once, can’t accidentally remove a different registration
that happens to share the same callback function.
Web additionally keeps off(event, callback) as a second mechanism (removes the first match
by reference) — mainly for compatibility with code written before Registration existed.
Dispatch is snapshotted
If a listener cancels its own registration (or calls off()) synchronously from inside its
own callback, that mutates the same listener list the dispatch loop is iterating. Every
implementation snapshots the listener list before iterating, so a self-cancelling callback
can’t shift a later listener into the just-removed slot and cause it to be skipped.
ready replays for late subscribers
The natives’ ready event fires once initialize fully settles. A listener that registers
after that point still receives it — a “replay latch” remembers that ready already fired
and invokes a late subscriber’s callback immediately, rather than requiring you to race the
subscription against initialization. Web has no ready event at all — see each platform’s
Events page for what to use instead.