Observers
React to reward, referral-state, and tier-progress changes in each platform's native idiom
Apps should react the moment referral state changes — confetti when a reward lands, a progress strip that updates live as a referee completes gates — without writing polling code. Every SDK exposes the same three change events in its platform's native idiom.
| Event | Fires when |
|---|---|
| rewards changed | A reward is earned, fulfilled, or claimed — delivers the changed rewards. |
| referral state changed | One of the user's referrals transitions (signed_up, validated, rejected). |
| tier progress changed | Points or referrals-to-next-tier change. |
Callbacks deliver the changed objects plus a diff hint, on the main thread on mobile. Each underlying change fires exactly once, even across overlapping refreshes. Changes reach the device via smart refresh — the SDK re-fetches on app foreground (tab focus on web), after SDK-initiated actions, and on a modest interval while observers are attached. No polling code, no persistent connection.
The SDK is an event emitter; on returns an unsubscribe function.
invitebase.on(event: InvitebaseEvent, handler: (payload) => void): () => void
invitebase.off(event: InvitebaseEvent, handler): void| Event | Payload |
|---|---|
rewardsChanged | { rewards: Reward[]; changed: Reward[] } |
referralStateChanged | { referral: { id: string; state: string } } |
tierProgressChanged | { progress: ReferrerProgress } |
const unsubscribe = invitebase.on('rewardsChanged', ({ changed }) => {
const fresh = changed.find((r) => r.status === 'available');
if (fresh) {
confetti();
toast(`You earned ${fresh.display.title}`);
}
});
// later
unsubscribe();Three equivalent idioms — delegate, AsyncSequence, and Combine — all delivering on the main thread.
Delegate
public protocol InvitebaseDelegate: AnyObject {
func rewardsDidChange(_ changed: [Reward])
func referralStateDidChange(_ referral: ReferralUpdate)
func tierProgressDidChange(_ progress: ReferrerProgress)
}
Invitebase.delegate = selfAsync sequences
Task {
for await changed in Invitebase.rewardUpdates {
handleRewards(changed)
}
}
// Also available:
// Invitebase.referralStateUpdates: AsyncStream<ReferralUpdate>
// Invitebase.tierProgressUpdates: AsyncStream<ReferrerProgress>Combine
import Combine
Invitebase.rewardsPublisher
.receive(on: DispatchQueue.main)
.sink { changed in handleRewards(changed) }
.store(in: &cancellables)
// Also available: referralStatePublisher, tierProgressPublisherTwo equivalent idioms — a listener interface and Kotlin Flows.
Listener
interface InvitebaseListener {
fun onRewardsChanged(changed: List<Reward>)
fun onReferralStateChanged(referral: ReferralUpdate)
fun onTierProgressChanged(progress: ReferrerProgress)
}
Invitebase.addListener(listener)
Invitebase.removeListener(listener)Flows
// Cold flows — collection activates the SDK's smart refresh
Invitebase.rewardUpdates: Flow<List<Reward>>
Invitebase.referralStateUpdates: Flow<ReferralUpdate>
Invitebase.tierProgressUpdates: Flow<ReferrerProgress>class RewardsViewModel : ViewModel() {
val toastEvents = MutableSharedFlow<String>()
init {
viewModelScope.launch {
Invitebase.rewardUpdates.collect { changed ->
changed.firstOrNull { it.status == RewardStatus.AVAILABLE }?.let {
toastEvents.emit("You earned ${it.title}")
}
}
}
}
}Observation is built into the hooks — components re-render automatically when the underlying state changes:
function RewardsScreen() {
// Re-renders the moment a reward is earned, fulfilled, or claimed
const { rewards } = useRewards({ status: 'available' });
// Updates live as referees complete gates
const { progress } = useTierProgress();
// Re-renders on attribution resolution and referral state transitions
const status = useReferralStatus();
// …
}There is no separate subscription API to manage. See the hooks reference.
Change observers are broadcast Streams. Listening activates the SDK's smart refresh.
static Stream<List<Reward>> Invitebase.rewardsChanged
static Stream<ReferralUpdate> Invitebase.referralStateChanged
static Stream<ReferrerProgress> Invitebase.tierProgressChanged_sub = Invitebase.rewardsChanged.listen((changed) {
final fresh = changed.where((r) => r.status == RewardStatus.available);
if (fresh.isNotEmpty) {
confettiController.play();
showToast('You earned ${fresh.first.title}');
}
});Or rebuild widgets directly:
StreamBuilder<ReferrerProgress>(
stream: Invitebase.tierProgressChanged,
builder: (context, snapshot) {
final p = snapshot.data;
if (p?.referralsToNextTier == null) return const SizedBox.shrink();
return Text('${p!.referralsToNextTier} more referrals to unlock ${p.nextTier}');
},
)