Notifications (React Native)
The React Native layer should treat notifications as data payloads and render UI in JavaScript.
Event Sources
Your native bridge should emit bubbl_notification in two cases:
- SDK local broadcast while app is running (
NotificationRouter.BROADCASTon Android,NotificationManager.publisheron iOS) - App opened from a push tap (
intent/userInfopayload forwarded by native layer)
Push Campaigns (No Geofence Required)
Push Campaigns are not tied to geofence entry/exit. If a user is eligible for the campaign, the host app should receive bubbl_notification and render the modal regardless of location state.
- Geofence updates (
bubbl_geofence) are optional for map/visualization. - Modal rendering should depend on
bubbl_notificationpayloads only.
Payload Routing Rules (Important)
For reliability across foreground/background/terminated flows:
- Android should parse push payload from
payload, thennotification_payload, thendata. - iOS should forward
userInfofrom bothdidReceiveRemoteNotificationandUNUserNotificationCenterdelegate callbacks. - Keep a short in-memory queue in native and flush when JS listeners are attached, so cold-start notifications are not dropped.
- Keep JS-side modal dedupe (short TTL) as a defensive guard even with SDK
2.2.6+.
Subscribe in React Native
import { useEffect } from 'react';
import { BubblBridge } from '@bubbl-tech/react-native-sdk';
export function useBubblNotifications(onPayload: (payload: any) => void) {
useEffect(() => {
const sub = BubblBridge.onNotification(onPayload);
return () => sub.remove();
}, [onPayload]);
}
Recommended Normalized Payload
type BubblNotificationPayload = {
id?: number;
headline?: string | null;
body?: string | null;
mediaUrl?: string | null;
mediaType?: string | null; // image | video | audio | survey
ctaLabel?: string | null;
ctaUrl?: string | null;
locationId?: string | null;
postMessage?: string | null;
questions?: Array<{
id: number;
question: string;
question_type?: string | null;
has_choices?: boolean;
position?: number;
choices?: Array<{ id: number; choice: string; position?: number }>;
}> | null;
raw?: string;
};
Track lifecycle events
Use bridge methods to report engagement:
// Notification CTA
await BubblBridge.cta(notificationId, locationId);
// Survey/event activity
await BubblBridge.trackSurveyEvent(String(notificationId), String(locationId), 'notification_delivered');
await BubblBridge.trackSurveyEvent(String(notificationId), String(locationId), 'cta_engagement');
await BubblBridge.trackSurveyEvent(String(notificationId), String(locationId), 'dismissed');
For survey modals, mirror native iOS/Android behavior:
- On survey modal open: track
notification_deliveredandcta_engagement. - On submit tap: call
submitSurveyResponse(...)only (do not send a separatesurvey_submittracking event). - On successful submit: close the modal.
Survey submission
await BubblBridge.submitSurveyResponse(String(notificationId), String(locationId), [
{
question_id: 42,
type: 'SINGLE_CHOICE',
value: 'Premium',
choice: [{ choice_id: 10 }],
},
]);
Compatibility note: if you post directly to dashboard /api/survey-response, backend now accepts activity: survey_submit as an alias and normalizes it to survey_activity.
Platform notes
- Android payload comes from
NotificationRouter.DomainNotificationJSON. - iOS payload is usually sourced from
BubblNotificationDetailsand may include extra metadata depending on your bridge mapper. - Keep
rawin payload for debugging and compatibility when schema evolves.