ChangelogEdit pageSTAFFOpen dashboard

Bubbl

enum · Swiftobject · Kotlinobject · TypeScriptabstract final class · Dart

The SDK's entry point. Every call your app makes to Bubbl goes through it, from start to diagnostics.

Bubbl holds every function of the SDK. You can call it from any thread. Calls made before start do nothing and log a warning, except permissions.openSettings(), which needs nothing from Bubbl. On a device below the runtime minimum (Android 8.1, iOS 17), isSupported is false and every call does nothing.

Base URLs before the 5.0 releaseUntil 5.0 is released, the Sandbox base URL is https://api.sandbox.staging.bubbl.tech and the Production base URL is https://api.staging.bubbl.tech.

Properties#

isSupported
Boolean
Whether Bubbl runs on this device: Android 8.1 (API 27) or newer. When false, start logs one line and returns, every other call does nothing, and no listener is ever called. @JvmStatic.
permissions
Bubbl.Permissions
The permissions Bubbl uses: their state, and asking for them. See Permissions. @JvmStatic.
isSupported
static var isSupported: Bool
Whether Bubbl works on this device: iOS 17 or later. When false, start logs one line and returns, every other call does nothing, permission calls return nil and listeners are never called.
permissions
static let permissions: BubblPermissions
The permissions Bubbl uses: their state, and asking for them. See Permissions.
sdkVersion
static let sdkVersion: String
This SDK's version. iOS only; on other platforms read sdkVersion from diagnostics.
isSupported
static Future<bool> get isSupported
Whether Bubbl works on this device: Android 8.1 (API 27) or iOS 17 and later. On older versions every call does nothing. An asynchronous getter: await Bubbl.isSupported.
permissions
static const BubblPermissions permissions
The permissions Bubbl uses: their state, and asking for them. See Permissions.
events
static Stream<BubblEvent> get events
A broadcast stream of every BubblEvent. addEventListener listens to this stream.
isSupported
boolean (getter)
Whether Bubbl works on this device: iOS 17 and later, Android 8.1 (API 27) and later. Synchronous. False where there's no native module (web, Expo Go, Jest, a build from before the install).
permissions
object
The permissions Bubbl uses: their state, and asking for them. See Permissions.

Start and stop#

Call start once per launch, early: in Application.onCreate on Android, in application(_:didFinishLaunchingWithOptions:) or the SwiftUI App init on iOS, in main() on Flutter, and at the top of index.js on React Native. Calling it again with the same key and options changes nothing. To start with a credential from pairing a phone with a code, see Starting with a device credential and BubblCredential.

start(context: Context, apiKey: String, options: BubblOptions)Android only
Starts Bubbl with the SDK API key. Keeps only the application context.
start(context: Context, credential: BubblCredential, options: BubblOptions)Android only
Starts Bubbl with a device credential instead of an API key: the device never registers itself. A different credential than last time, or a switch from an API key, starts afresh as a new device.
stop()Android only
Stops Bubbl on this device until start is called again: no geofences, no background work, nothing shown. Unlike optOut(), the server isn't told and nothing is dropped.
Android throws on a bad startstart throws IllegalArgumentException for an empty API key (Bubbl.start: the API key is empty), an incomplete credential (Bubbl.start: the credential is incomplete) or a base URL that isn't allowed (Bubbl.start: baseUrl must be https:// (http:// only for localhost or a .test host)). The other platforms log the same text instead.
static func start(apiKey: String, options: BubblOptions)iOS only
Starts Bubbl with the SDK API key. A mistake (an empty key, a base URL that isn't allowed) is logged, never thrown.
static func start(credential: BubblCredential, options: BubblOptions)iOS only
Starts Bubbl with a device credential instead of an API key: the device never registers itself. A different credential than last time, or a switch from an API key, starts afresh as a new device.
static func stop()iOS only
Stops Bubbl on this device until start is called again. Nothing is dropped, and the server isn't told (unlike optOut()).
static Future<void> start({required String apiKey, required BubblOptions options})Flutter only
Starts Bubbl with the SDK API key. A mistake is logged natively, never thrown. When the OS wakes the app for a geofence or a push, the native SDK starts itself from its saved settings before any Dart runs.
static Future<void> startWithCredential(BubblCredential credential, {required BubblOptions options})Flutter only
Starts Bubbl with a device credential instead of an API key. A separate name, because Dart has no overloads.
static Future<void> stop()Flutter only
Stops Bubbl on this device until start again. Nothing is dropped, and the server isn't told.
start(apiKey: string, options: BubblOptions): voidReact Native only
Starts Bubbl with the SDK API key. A mistake is logged natively, never thrown. When the OS wakes the app for a geofence or a push, the native SDK starts itself without JavaScript.
startWithCredential(credential: BubblCredential, options: BubblOptions): voidReact Native only
Starts Bubbl with a device credential instead of an API key. The secret goes to the native SDK only.
stop(): voidReact Native only
Stops Bubbl on this device until start again. Nothing is dropped, and the server isn't told.

For apps that ask their users first, start with requireConsent in BubblOptions. More in Consent and user data.

setConsent(granted: Boolean)Android only
The user's answer, for apps started with requireConsent (and to give consent again after an opt-out). true starts everything; false is the same as optOut().
optOut()Android only
Stops Bubbl for this user: nothing more is sent, shown or tracked, and the server is told.
deleteMyData()Android only
Erases this device and everything Bubbl recorded about it, on the server and on the device. Keeps trying until it's done. Bubbl stays off afterwards; a later setConsent(true) starts afresh as a new device.
static func setConsent(_ granted: Bool)iOS only
The user's answer, for apps started with requireConsent (and to give consent again after an opt-out). true starts everything; false is the same as optOut().
static func optOut()iOS only
Stops Bubbl for this user: nothing more is sent, shown or tracked, what's queued is dropped, and the server is told.
static func deleteMyData()iOS only
Erases this device and everything Bubbl recorded about it, on the server and on the device. Keeps trying until it's done. Bubbl stays off afterwards; a later setConsent(true) starts afresh as a new device.
static Future<void> setConsent(bool granted)Flutter only
The user's answer, for apps started with requireConsent. true starts everything; false is the same as optOut().
static Future<void> optOut()Flutter only
Stops Bubbl for this user: nothing more is sent, shown or tracked, and the server is told.
static Future<void> deleteMyData()Flutter only
Erases this device and everything Bubbl recorded about it, on the server and on the device. Bubbl stays off afterwards.
setConsent(granted: boolean): voidReact Native only
For requireConsent: true starts everything; false is the same as optOut().
optOut(): voidReact Native only
Stops Bubbl for this user; the server is told.
deleteMyData(): voidReact Native only
Erases the device on the server and on the device. Bubbl stays off afterwards.

Permissions#

Bubbl.permissions reads where the device stands and asks for notifications and location, showing Bubbl's privacy view first when the dashboard says so. Its functions are listed in Permissions.

Location#

Turning location off stops geofences only; the rest of Bubbl carries on. More in Location and geofences.

setLocationEnabled(enabled: Boolean)Android only
Turns Bubbl's use of location (geofences) off, or on again.
static func setLocationEnabled(_ enabled: Bool)iOS only
Turns Bubbl's use of location (geofences) off, or on again.
static Future<void> setLocationEnabled(bool enabled)Flutter only
Turns Bubbl's use of location (geofences) off, or on again.
setLocationEnabled(enabled: boolean): voidReact Native only
Turns Bubbl's use of location (geofences) off, or on again.

Segments and events#

A custom event's name is 1 to 100 characters of A–Z a–z 0–9 . _ : -. It takes up to 50 flat properties: text, numbers, true/false or null. An invalid name is logged and dropped; a property of another type is left out. Event listeners receive BubblEvents on the main thread. More in Segments and events.

setSegments(segments: List<String>)Android only
The device's segments, for targeting campaigns. Replaces any set before.
track(name: String, properties: Map<String, Any?> = emptyMap())Android only
Records a custom event. @JvmOverloads, so Java can leave out properties.
addEventListener(listener: BubblEventListener)Android only
Adds a listener for BubblEvents.
removeEventListener(listener: BubblEventListener)Android only
Removes a listener added with addEventListener.
static func setSegments(_ segments: [String])iOS only
The device's segments, for targeting campaigns. Replaces any set before.
static func track(_ name: String, properties: [String: Any?] = [:])iOS only
Records a custom event.
@discardableResult static func addEventListener(_ listener: @escaping @MainActor @Sendable (BubblEvent) -> Void) -> BubblEventTokeniOS only
Adds a listener for BubblEvents. Keep the token to remove it with: Swift closures have no identity.
static func removeEventListener(_ token: BubblEventToken)iOS only
Removes the listener that returned token.
static Future<void> setSegments(List<String> segments)Flutter only
The device's segments, for targeting campaigns. Replaces any set before.
static Future<void> track(String name, [Map<String, Object?> properties = const <String, Object?>{}])Flutter only
Records a custom event. properties is an optional positional argument.
static StreamSubscription<BubblEvent> addEventListener(void Function(BubblEvent event) listener)Flutter only
Listens to Bubbl.events. Keep the subscription to stop listening.
static Future<void> removeEventListener(StreamSubscription<BubblEvent> subscription)Flutter only
Cancels a subscription addEventListener returned.
setSegments(segments: string[]): voidReact Native only
The device's segments, for targeting campaigns. Replaces any set before.
track(name: string, properties?: Record<string, string | number | boolean | null>): voidReact Native only
Records a custom event. properties defaults to {}.
addEventListener(listener: EventListener): SubscriptionReact Native only
Adds a listener for BubblEvents. There's no removeEventListener: call remove() on the returned subscription.

Notifications#

For an app that draws notifications itself or keeps its own inbox. The listener returns true to show a notification itself; the app then reports what happens with the report functions. BubblMessage has the message's fields and the survey answer formats. More in Drawing notifications yourself.

setNotificationListener(listener: BubblNotificationListener?)Android only
Asked about each notification before Bubbl shows it, on the main thread: return true to show it yourself, false to let Bubbl. null goes back to Bubbl showing everything.
present(message: BubblMessage)Android only
Shows message in Bubbl's notification screen, for example one the listener held back.
reportDisplayed(message: BubblMessage)Android only
The app showed the notification.
reportOpened(message: BubblMessage)Android only
The user opened it (tapped it, rather than it opening on its own).
reportCtaClicked(message: BubblMessage)Android only
The user tapped its call to action.
reportDismissed(message: BubblMessage)Android only
The user closed it without acting.
reportMediaViewed(message: BubblMessage)Android only
Its media (video, audio, a YouTube video, an image) was shown or started playing.
reportMediaCompleted(message: BubblMessage, durationSeconds: Double)Android only
Its video or audio played to the end, durationSeconds long.
reportSurveyStarted(message: BubblMessage)Android only
The user started answering its survey.
submitSurvey(message: BubblMessage, answers: Map<String, Any?>): BooleanAndroid only
Sends the answers to a survey the app showed itself, by question id. Checked as the server checks them: false, with the reason logged, when they can't be sent.
openCta(message: BubblMessage)Android only
Opens the call to action as Bubbl's screen would, and reports the click.
static func setNotificationListener(_ listener: (@MainActor @Sendable (BubblMessage) -> Bool)?)iOS only
Asked about each notification before Bubbl shows it, on the main thread: return true to show it yourself, false to let Bubbl. nil goes back to Bubbl showing everything.
static func present(_ message: BubblMessage)iOS only
Shows message in Bubbl's notification screen, for example one the listener held back.
static func reportDisplayed(_ message: BubblMessage)iOS only
The app showed the notification.
static func reportOpened(_ message: BubblMessage)iOS only
The user opened it (tapped it, rather than it opening on its own).
static func reportCtaClicked(_ message: BubblMessage)iOS only
The user tapped its call to action.
static func reportDismissed(_ message: BubblMessage)iOS only
The user closed it without acting.
static func reportMediaViewed(_ message: BubblMessage)iOS only
Its media (video, audio, a YouTube video, an image) was shown or started playing.
static func reportMediaCompleted(_ message: BubblMessage, durationSeconds: Double)iOS only
Its video or audio played to the end, durationSeconds long.
static func reportSurveyStarted(_ message: BubblMessage)iOS only
The user started answering its survey.
@discardableResult static func submitSurvey(_ message: BubblMessage, answers: [String: Any?]) -> BooliOS only
Sends the answers to a survey the app showed itself, by question id. Checked as the server checks them: false, with the reason logged, when they can't be sent.
static func openCta(_ message: BubblMessage)iOS only
Opens the call to action as Bubbl's screen would, and reports the click.
static Future<void> setNotificationListener(FutureOr<bool> Function(BubblMessage message)? listener)Flutter only
Asked about each notification before Bubbl shows it, while the app is on screen: return true to show it yourself, false to let Bubbl. With the app in the background or closed, the listener isn't asked and Bubbl shows its system notification. If the listener throws, Bubbl shows the notification. null goes back to Bubbl showing everything.
static Future<void> present(BubblMessage message)Flutter only
Shows message in Bubbl's notification screen.
static Future<void> reportDisplayed(BubblMessage message)Flutter only
The app showed the notification.
static Future<void> reportOpened(BubblMessage message)Flutter only
The user opened it.
static Future<void> reportCtaClicked(BubblMessage message)Flutter only
The user tapped its call to action.
static Future<void> reportDismissed(BubblMessage message)Flutter only
The user closed it without acting.
static Future<void> reportMediaViewed(BubblMessage message)Flutter only
Its media was shown or started playing.
static Future<void> reportMediaCompleted(BubblMessage message, double durationSeconds)Flutter only
Its video or audio played to the end, durationSeconds long.
static Future<void> reportSurveyStarted(BubblMessage message)Flutter only
The user started answering its survey.
static Future<bool> submitSurvey(BubblMessage message, Map<String, Object?> answers)Flutter only
Sends the answers to a survey the app showed itself, by question id. false, with the reason logged natively, when they can't be sent.
static Future<void> openCta(BubblMessage message)Flutter only
Opens the call to action as Bubbl's screen would, and reports the click.
setNotificationListener(listener: NotificationListener | null): voidReact Native only
Asked about each notification before Bubbl shows it, while the app is open: return true (or a promise of true) to show it yourself. Anything else, or a throw, lets Bubbl show it. With the app in the background it isn't asked. null goes back to Bubbl showing everything.
present(message: BubblMessage): voidReact Native only
Shows message in Bubbl's notification screen.
reportDisplayed(message: BubblMessage): voidReact Native only
The app showed the notification.
reportOpened(message: BubblMessage): voidReact Native only
The user opened it.
reportCtaClicked(message: BubblMessage): voidReact Native only
The user tapped its call to action.
reportDismissed(message: BubblMessage): voidReact Native only
The user closed it without acting.
reportMediaViewed(message: BubblMessage): voidReact Native only
Its media was shown or started playing.
reportMediaCompleted(message: BubblMessage, durationSeconds: number): voidReact Native only
Its video or audio played to the end, durationSeconds long.
reportSurveyStarted(message: BubblMessage): voidReact Native only
The user started answering its survey.
submitSurvey(message: BubblMessage, answers: SurveyAnswers): Promise<boolean>React Native only
Sends the answers to a survey the app showed itself, by question id. false, with the reason logged natively, when they can't be sent.
openCta(message: BubblMessage): voidReact Native only
Opens the call to action as Bubbl would, and reports it.

Push and iOS push hooks#

isBubblMessage tells your own push handling to skip Bubbl's pushes. More in Push notifications.

isBubblMessage(data: Map<String, String>): BooleanAndroid only
Whether a push's data is Bubbl's, for example in your own FirebaseMessagingService.onMessageReceived. Android needs no push hooks: Bubbl receives its pushes itself.
static func isBubblMessage(_ userInfo: [AnyHashable: Any]) -> BooliOS only
Whether a push's payload is Bubbl's.
static func setPushToken(_ deviceToken: Data)iOS only
Hands Bubbl the APNs device token from application(_:didRegisterForRemoteNotificationsWithDeviceToken:). Works before start too.
static func willPresent(_ notification: UNNotification, completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) -> BooliOS only
From userNotificationCenter(_:willPresent:withCompletionHandler:). true when the push is Bubbl's, and Bubbl calls completionHandler; false when it isn't, and your code decides.
static func didReceive(_ response: UNNotificationResponse, completionHandler: @escaping () -> Void) -> BooliOS only
From userNotificationCenter(_:didReceive:withCompletionHandler:). true when the tapped notification is Bubbl's, and Bubbl calls completionHandler; false when it isn't.

Bubbl picks up the device token and hooks into your notification delegate by itself. Call setPushToken, willPresent and didReceive only when you set BubblAutoIntegrationEnabled to NO in Info.plist.

static bool isBubblMessage(Map<String, Object?> data)Flutter only
Whether a push's data is Bubbl's, for example in a firebase_messaging handler. Synchronous, and checked in Dart: true when data['bubbl'] is the string '1'.

The iOS push hooks aren't in Dart. With BubblAutoIntegrationEnabled set to NO in Info.plist, add import bubbl_flutter_sdk to ios/Runner/AppDelegate.swift and call the Swift Bubbl.setPushToken, Bubbl.willPresent and Bubbl.didReceive there, as on iOS.

isBubblMessage(data: Record<string, unknown> | null | undefined): booleanReact Native only
Whether a push's data is Bubbl's, for example in a React Native Firebase handler. Checked in JavaScript: true when data.bubbl is the string '1'.

The iOS push hooks aren't in JavaScript. With BubblAutoIntegrationEnabled set to NO in Info.plist, call them from your AppDelegate: in Swift, import BubblReactNativeSdk and call Bubbl.setPushToken, Bubbl.willPresent and Bubbl.didReceive as on iOS. From Objective-C, use the BubblPush class from <BubblReactNativeSdk/BubblReactNativeSdk-Swift.h>:

+ (void)setPushToken:(NSData *)deviceTokenReact Native only
The same as Bubbl.setPushToken(_:).
+ (BOOL)willPresent:(UNNotification *)notification completionHandler:(void (^)(UNNotificationPresentationOptions))completionHandlerReact Native only
The same as Bubbl.willPresent(_:completionHandler:): YES for Bubbl's pushes.
+ (BOOL)didReceive:(UNNotificationResponse *)response completionHandler:(void (^)(void))completionHandlerReact Native only
The same as Bubbl.didReceive(_:completionHandler:): YES for Bubbl's pushes.
+ (BOOL)isBubblMessage:(NSDictionary *)userInfoReact Native only
The same as Bubbl.isBubblMessage(_:).

Sandbox#

A Sandbox install (pk_test_ key) is pending until it's approved as a test device. registerTestDevice approves it with a single-use registration code from the dashboard (Configuration › Test devices). It never throws: the result says whether it worked. More in Sandbox and Production.

suspend fun registerTestDevice(code: String): Bubbl.TestDeviceResultAndroid only
Approves this device as a Sandbox test device with code.
registerTestDevice(code: String, callback: Bubbl.Callback<Bubbl.TestDeviceResult>)Android only
The same, with the result delivered on the main thread (for Java). @JvmStatic.
Bubbl.TestDeviceResult.approved
Boolean
The device is now an approved test device.
Bubbl.TestDeviceResult.message
String?
Why not, when not approved: a wrong, used or expired code, a full Sandbox, no connection, or Bubbl not started.
static func registerTestDevice(_ code: String) async -> BubblTestDeviceResultiOS only
Approves this device as a Sandbox test device with code.
static func registerTestDevice(_ code: String, completion: @escaping @Sendable @MainActor (BubblTestDeviceResult) -> Void)iOS only
The same, for code that can't await. completion runs on the main thread.
BubblTestDeviceResult.approved
Bool
The device is now an approved test device.
BubblTestDeviceResult.message
String?
Why not, when not approved: the server's reason (a wrong, used or expired code, the Sandbox's test devices all taken) or the SDK's (before start, no network, below iOS 17). nil when approved.
static Future<BubblTestDeviceResult> registerTestDevice(String code)Flutter only
Approves this device as a Sandbox test device with code. Never throws.
BubblTestDeviceResult.approved
bool
The device is now an approved test device.
BubblTestDeviceResult.message
String?
Why not, when not approved. null when approved.
registerTestDevice(code: string): Promise<BubblTestDeviceResult>React Native only
Approves this device as a Sandbox test device with code. Never throws; without a native module it resolves not approved, with the reason.
approved
boolean
The device is now an approved test device.
message
string (optional)
Why not, when not approved. Left out when there's no reason.

Diagnostics#

Where Bubbl stands on this device, for support and for your own debug screen. The fields are listed in Diagnostics.

suspend fun diagnostics(): Bubbl.DiagnosticsAndroid only
Where Bubbl stands now.
diagnostics(callback: Bubbl.Callback<Bubbl.Diagnostics>)Android only
The same, with the result delivered on the main thread (for Java). @JvmStatic.
static func diagnostics() async -> Bubbl.DiagnosticsiOS only
Where Bubbl stands now.
static func diagnostics(completion: @escaping @Sendable @MainActor (Bubbl.Diagnostics) -> Void)iOS only
The same, for code that can't await. completion runs on the main thread.
static Future<BubblDiagnostics> diagnostics()Flutter only
Where Bubbl stands now.
diagnostics(): Promise<BubblDiagnostics>React Native only
Where Bubbl stands now. Without a native module it still resolves: not started, with the reason in lastError.

Listener and callback types#

fun interface Bubbl.Callback<T> { fun onResult(value: T) }Android only
A result delivered on the main thread, for Java and for code that doesn't use coroutines.
fun interface BubblEventListener { fun onEvent(event: BubblEvent) }Android only
Takes each BubblEvent, on the main thread.
fun interface BubblNotificationListener { fun onNotification(message: BubblMessage): Boolean }Android only
Return true when the app shows message itself. Called on the main thread.
BubblEventToken
struct BubblEventToken: Hashable, Sendable
What addEventListener returns, to remove that listener with. It has no public fields.

Event listeners are plain Dart functions, void Function(BubblEvent event), and the notification listener is FutureOr<bool> Function(BubblMessage message). addEventListener returns a StreamSubscription<BubblEvent> from dart:async.

EventListener
(event: BubblEvent) => void
An event listener. A throw is caught and logged to the console.
NotificationListener
(message: BubblMessage) => boolean | Promise<boolean>
Return true (or a promise of true) to show the message yourself.
Subscription
{ remove(): void }
What addEventListener returns: call remove() to stop listening.

The package exports Bubbl both as a named export and as the default export, and every type on these pages as a type export.