B Bubbl Docs

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:

  1. SDK local broadcast while app is running (NotificationRouter.BROADCAST on Android, NotificationManager.publisher on iOS)
  2. App opened from a push tap (intent/userInfo payload 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_notification payloads only.

Payload Routing Rules (Important)

For reliability across foreground/background/terminated flows:

  • Android should parse push payload from payload, then notification_payload, then data.
  • iOS should forward userInfo from both didReceiveRemoteNotification and UNUserNotificationCenter delegate 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]);
}
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_delivered and cta_engagement.
  • On submit tap: call submitSurveyResponse(...) only (do not send a separate survey_submit tracking 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.DomainNotification JSON.
  • iOS payload is usually sourced from BubblNotificationDetails and may include extra metadata depending on your bridge mapper.
  • Keep raw in payload for debugging and compatibility when schema evolves.