Quickstart
Add the SDK, start it with your Sandbox key, approve your phone as a test device and check it works. About 15 minutes per platform.
You need a Bubbl dashboard login with access to Configuration (owners, admins and developers have it), and a real phone: Android 8.1 or later with Google Play services, or an iPhone on iOS 17 or later. Choose your platform in the sidebar and follow its steps. Everything here uses your Sandbox workspace, which is free and only serves the test phones you approve.
https://api.sandbox.staging.bubbl.tech and Production at https://api.staging.bubbl.tech. Use those in place of the hosts in the code below for now. The API address shown under your key in Configuration › Developers is the one for your workspace.Android#
- 1Install the SDKCheck that
mavenCentral()andgoogle()are in your repositories insettings.gradle.kts, then addtech.bubbl.sdk:bubbl-sdkto your app module and compile with SDK 35. You don't edit your manifest: the SDK's own manifest merges in the permissions, receivers and screens it needs, and it needs no R8 or ProGuard rules.BUILD.GRADLE.KTSandroid { compileSdk = 35 } dependencies { implementation("tech.bubbl.sdk:bubbl-sdk:5.0.0") }PACKAGEXcode › File › Add Package Dependencies… Package URL: https://github.com/bubbl-public/bubbl-ios-sdk Dependency rule: Exact Version 5.0.0 Product: BubblSDK (add it to your app target)PUBSPEC.YAMLdependencies: flutter: sdk: flutter bubbl_flutter_sdk: 5.0.0INSTALLnpm install @bubblsdk/react-native-sdk@5.0.0 cd ios && pod install - 2Start Bubbl in Application.onCreateIn the dashboard, open Configuration › Developers, copy the SDK API key (a Sandbox key starts
pk_test_) and note the API address shown under it. CallBubbl.startwith both in yourApplicationclass'sonCreate, and register the class inAndroidManifest.xml. Start it there, not in an activity: Android wakes your app in the background for geofences and pushes, and onlyApplication.onCreateruns then.APP.KTimport android.app.Application import tech.bubbl.sdk.Bubbl import tech.bubbl.sdk.BubblOptions class App : Application() { override fun onCreate() { super.onCreate() Bubbl.start(this, "pk_test_…", BubblOptions(baseUrl = "https://api.sandbox.bubbl.tech")) } }ANDROIDMANIFEST.XML<application android:name=".App" ...>MYAPP.SWIFTimport BubblSDK import SwiftUI @main struct MyApp: App { init() { Bubbl.start(apiKey: "pk_test_…", options: BubblOptions(baseUrl: "https://api.sandbox.bubbl.tech")) } var body: some Scene { WindowGroup { ContentView() } } }APPDELEGATE.SWIFTimport BubblSDK import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Bubbl.start(apiKey: "pk_test_…", options: BubblOptions(baseUrl: "https://api.sandbox.bubbl.tech")) return true } }MAIN.DARTimport 'package:bubbl_flutter_sdk/bubbl_flutter_sdk.dart'; import 'package:flutter/widgets.dart'; Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await Bubbl.start( apiKey: 'pk_test_…', options: const BubblOptions(baseUrl: 'https://api.sandbox.bubbl.tech'), ); runApp(const MyApp()); }INDEX.JSimport { Bubbl } from '@bubblsdk/react-native-sdk'; Bubbl.start('pk_test_…', { baseUrl: 'https://api.sandbox.bubbl.tech' }); // …then your app's usual AppRegistry.registerComponent(…) - 3Run the app and approve the phoneRun the app on your phone. A Sandbox install waits as pending until you approve it, and Bubbl logs its code in Logcat under the tag
BubblSDK. In the dashboard, open Configuration › Test Devices and approve the phone with that code in the Pending list, or choose Add a test device and approve it as it appears.LOGCAT (TAG BUBBLSDK)Bubbl sandbox: approve this device with code K7Q-2MXXCODE CONSOLE (TECH.BUBBL.SDK)Bubbl sandbox: approve this device with code K7Q-2MXLOGCAT / XCODE CONSOLEBubbl sandbox: approve this device with code K7Q-2MXLOGCAT / XCODE CONSOLEBubbl sandbox: approve this device with code K7Q-2MX - 4Ask for permissionsAsk for notifications and location when it suits your app, for example after your own onboarding.
requestLocation(always = true)also handles Android's separate "Allow all the time" step, which geofences need to work with the app closed. Bubbl shows its privacy view first when the dashboard's privacy setting says so, then Android's prompt.MAINACTIVITY.KT// From a coroutine, for example lifecycleScope.launch { … } after your onboarding screen val notifications = Bubbl.permissions.requestNotifications() // Android 13+; nothing to ask before that val location = Bubbl.permissions.requestLocation(always = true) // Or with a callback (also from Java) Bubbl.permissions.requestLocation(true) { status -> /* BubblPermissionStatus? */ }SWIFTlet notifications = await Bubbl.permissions.requestNotifications() let location = await Bubbl.permissions.requestLocation(always: true) // Or with a completion handler (called on the main thread) Bubbl.permissions.requestLocation(always: true) { status in /* BubblPermissionStatus? */ }DARTawait Bubbl.permissions.requestNotifications(); await Bubbl.permissions.requestLocation(always: true);TSawait Bubbl.permissions.requestNotifications(); await Bubbl.permissions.requestLocation({ always: true }); - 5Connect pushUpload your Firebase project's service account under Configuration › Firebase › Credentials. Then either keep your own
google-services.jsonwith thecom.google.gms.google-servicesGradle plugin, or upload the file under Android apps on the same page and leave Firebase out of your app. You don't write any push code. See Push notifications for the details. - 6Check it worksRead
Bubbl.diagnosticsand check thatactiveandregisteredare true. OncehasPushTokenis true too, open the Active users card on the dashboard's home page, search for the phone'sinstallIdand choose Send test push. It arrives as a plain notification that opens your app.KOTLINBubbl.diagnostics { d -> Log.i("App", "installId=${d.installId} active=${d.active} registered=${d.registered} " + "push=${d.hasPushToken} geofences=${d.geofences} lastError=${d.lastError}") }SWIFTlet d = await Bubbl.diagnostics() print(d.installId ?? "none", d.active, d.registered, d.hasPushToken, d.geofences, d.lastError ?? "none")DARTfinal d = await Bubbl.diagnostics(); debugPrint('installId=${d.installId} active=${d.active} registered=${d.registered} ' 'push=${d.hasPushToken} geofences=${d.geofences} lastError=${d.lastError}');TSconst d = await Bubbl.diagnostics(); console.log(d.installId, d.active, d.registered, d.hasPushToken, d.geofences, d.lastError);
iOS#
- 1Add the packageIn Xcode, choose File › Add Package Dependencies…, enter
https://github.com/bubbl-public/bubbl-ios-sdk, set the rule to the exact version shown in the pane, and add the BubblSDK product to your app target. SDK 5 comes through Swift Package Manager only. It needs Xcode 16 or later.BUILD.GRADLE.KTSandroid { compileSdk = 35 } dependencies { implementation("tech.bubbl.sdk:bubbl-sdk:5.0.0") }PACKAGEXcode › File › Add Package Dependencies… Package URL: https://github.com/bubbl-public/bubbl-ios-sdk Dependency rule: Exact Version 5.0.0 Product: BubblSDK (add it to your app target)PUBSPEC.YAMLdependencies: flutter: sdk: flutter bubbl_flutter_sdk: 5.0.0INSTALLnpm install @bubblsdk/react-native-sdk@5.0.0 cd ios && pod install - 2Add location strings and the push capabilityAdd the two location usage strings to your app's
Info.plist: iOS shows them in its prompts. Then add the Push Notifications capability under Signing & Capabilities. You don't need any Background Modes for Bubbl: geofences and significant location changes relaunch the app without them.INFO.PLIST<key>NSLocationWhenInUseUsageDescription</key> <string>We use your location to show offers for places near you.</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>We use your location, even when the app is closed, to show offers when you arrive at a place.</string>INFO.PLIST<key>NSLocationWhenInUseUsageDescription</key> <string>We use your location to show offers for places near you.</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>We use your location, even when the app is closed, to show offers when you arrive at a place.</string>INFO.PLIST<key>NSLocationWhenInUseUsageDescription</key> <string>We use your location to show offers for places near you.</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>We use your location, even when the app is closed, to show offers when you arrive at a place.</string> - 3Start Bubbl at launchIn the dashboard, open Configuration › Developers, copy the SDK API key (a Sandbox key starts
pk_test_) and note the API address shown under it. CallBubbl.startwith both in your SwiftUIApp'sinit, or inapplication(_:didFinishLaunchingWithOptions:)in UIKit. Bubbl never crashes your app: a mistake such as an empty key or anhttp://address is logged, not thrown.APP.KTimport android.app.Application import tech.bubbl.sdk.Bubbl import tech.bubbl.sdk.BubblOptions class App : Application() { override fun onCreate() { super.onCreate() Bubbl.start(this, "pk_test_…", BubblOptions(baseUrl = "https://api.sandbox.bubbl.tech")) } }ANDROIDMANIFEST.XML<application android:name=".App" ...>MYAPP.SWIFTimport BubblSDK import SwiftUI @main struct MyApp: App { init() { Bubbl.start(apiKey: "pk_test_…", options: BubblOptions(baseUrl: "https://api.sandbox.bubbl.tech")) } var body: some Scene { WindowGroup { ContentView() } } }APPDELEGATE.SWIFTimport BubblSDK import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Bubbl.start(apiKey: "pk_test_…", options: BubblOptions(baseUrl: "https://api.sandbox.bubbl.tech")) return true } }MAIN.DARTimport 'package:bubbl_flutter_sdk/bubbl_flutter_sdk.dart'; import 'package:flutter/widgets.dart'; Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await Bubbl.start( apiKey: 'pk_test_…', options: const BubblOptions(baseUrl: 'https://api.sandbox.bubbl.tech'), ); runApp(const MyApp()); }INDEX.JSimport { Bubbl } from '@bubblsdk/react-native-sdk'; Bubbl.start('pk_test_…', { baseUrl: 'https://api.sandbox.bubbl.tech' }); // …then your app's usual AppRegistry.registerComponent(…) - 4Run the app and approve the phoneRun a signed build on your iPhone (iOS gives an unsigned build no Keychain, and Bubbl can't keep its credential without one). A Sandbox install waits as pending until you approve it, and Bubbl logs its code to the Xcode console. In the dashboard, open Configuration › Test Devices and approve the phone with that code in the Pending list, or choose Add a test device and approve it as it appears.LOGCAT (TAG BUBBLSDK)
Bubbl sandbox: approve this device with code K7Q-2MXXCODE CONSOLE (TECH.BUBBL.SDK)Bubbl sandbox: approve this device with code K7Q-2MXLOGCAT / XCODE CONSOLEBubbl sandbox: approve this device with code K7Q-2MXLOGCAT / XCODE CONSOLEBubbl sandbox: approve this device with code K7Q-2MX - 5Ask for permissionsAsk for notifications and location when it suits your app.
requestLocation(always: true)asks for "While Using" and then the second step to "Always", which geofences need to work with the app closed. You don't callregisterForRemoteNotifications()yourself: Bubbl asks iOS for the device token once notifications are allowed.MAINACTIVITY.KT// From a coroutine, for example lifecycleScope.launch { … } after your onboarding screen val notifications = Bubbl.permissions.requestNotifications() // Android 13+; nothing to ask before that val location = Bubbl.permissions.requestLocation(always = true) // Or with a callback (also from Java) Bubbl.permissions.requestLocation(true) { status -> /* BubblPermissionStatus? */ }SWIFTlet notifications = await Bubbl.permissions.requestNotifications() let location = await Bubbl.permissions.requestLocation(always: true) // Or with a completion handler (called on the main thread) Bubbl.permissions.requestLocation(always: true) { status in /* BubblPermissionStatus? */ }DARTawait Bubbl.permissions.requestNotifications(); await Bubbl.permissions.requestLocation(always: true);TSawait Bubbl.permissions.requestNotifications(); await Bubbl.permissions.requestLocation({ always: true }); - 6Connect pushUnder Configuration › Apple Push, add your app with its Bundle ID, an APNs auth key (.p8) from developer.apple.com › Keys with Apple Push Notifications enabled, its Key ID and your Team ID, then choose Check and save. Bubbl sends iOS pushes straight to Apple, without Firebase. See Push notifications for the details.
- 7Check it worksRead
Bubbl.diagnostics()and check thatactiveandregisteredare true. OncehasPushTokenis true too, open the Active users card on the dashboard's home page, search for the phone'sinstallIdand choose Send test push. A development-signed Xcode build gets it through Apple's sandbox environment.KOTLINBubbl.diagnostics { d -> Log.i("App", "installId=${d.installId} active=${d.active} registered=${d.registered} " + "push=${d.hasPushToken} geofences=${d.geofences} lastError=${d.lastError}") }SWIFTlet d = await Bubbl.diagnostics() print(d.installId ?? "none", d.active, d.registered, d.hasPushToken, d.geofences, d.lastError ?? "none")DARTfinal d = await Bubbl.diagnostics(); debugPrint('installId=${d.installId} active=${d.active} registered=${d.registered} ' 'push=${d.hasPushToken} geofences=${d.geofences} lastError=${d.lastError}');TSconst d = await Bubbl.diagnostics(); console.log(d.installId, d.active, d.registered, d.hasPushToken, d.geofences, d.lastError);
Flutter#
- 1Add the packageAdd
bubbl_flutter_sdktopubspec.yamlwith the exact version shown, then runflutter pub get. Name the version: SDK 5 is a pre-release, so a plainflutter pub add bubbl_flutter_sdkpicks the latest stable 4.x instead. The plugin carries the native SDKs, so there's no separate Gradle dependency, pod or Swift package to add.BUILD.GRADLE.KTSandroid { compileSdk = 35 } dependencies { implementation("tech.bubbl.sdk:bubbl-sdk:5.0.0") }PACKAGEXcode › File › Add Package Dependencies… Package URL: https://github.com/bubbl-public/bubbl-ios-sdk Dependency rule: Exact Version 5.0.0 Product: BubblSDK (add it to your app target)PUBSPEC.YAMLdependencies: flutter: sdk: flutter bubbl_flutter_sdk: 5.0.0INSTALLnpm install @bubblsdk/react-native-sdk@5.0.0 cd ios && pod install - 2Start Bubbl in main()In the dashboard, open Configuration › Developers, copy the SDK API key (a Sandbox key starts
pk_test_) and note the API address shown under it. CallBubbl.startwith both inmain(), beforerunApp. When the OS wakes your app for a geofence or a push, the native SDK starts itself from its saved settings before any Dart runs.APP.KTimport android.app.Application import tech.bubbl.sdk.Bubbl import tech.bubbl.sdk.BubblOptions class App : Application() { override fun onCreate() { super.onCreate() Bubbl.start(this, "pk_test_…", BubblOptions(baseUrl = "https://api.sandbox.bubbl.tech")) } }ANDROIDMANIFEST.XML<application android:name=".App" ...>MYAPP.SWIFTimport BubblSDK import SwiftUI @main struct MyApp: App { init() { Bubbl.start(apiKey: "pk_test_…", options: BubblOptions(baseUrl: "https://api.sandbox.bubbl.tech")) } var body: some Scene { WindowGroup { ContentView() } } }APPDELEGATE.SWIFTimport BubblSDK import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Bubbl.start(apiKey: "pk_test_…", options: BubblOptions(baseUrl: "https://api.sandbox.bubbl.tech")) return true } }MAIN.DARTimport 'package:bubbl_flutter_sdk/bubbl_flutter_sdk.dart'; import 'package:flutter/widgets.dart'; Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await Bubbl.start( apiKey: 'pk_test_…', options: const BubblOptions(baseUrl: 'https://api.sandbox.bubbl.tech'), ); runApp(const MyApp()); }INDEX.JSimport { Bubbl } from '@bubblsdk/react-native-sdk'; Bubbl.start('pk_test_…', { baseUrl: 'https://api.sandbox.bubbl.tech' }); // …then your app's usual AppRegistry.registerComponent(…) - 3iOS: add location strings and the push capabilityAdd the two location usage strings to
ios/Runner/Info.plist, and the Push Notifications capability to the Runner target under Signing & Capabilities. Android needs nothing extra: the SDK's manifest merges in what it needs.INFO.PLIST<key>NSLocationWhenInUseUsageDescription</key> <string>We use your location to show offers for places near you.</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>We use your location, even when the app is closed, to show offers when you arrive at a place.</string>INFO.PLIST<key>NSLocationWhenInUseUsageDescription</key> <string>We use your location to show offers for places near you.</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>We use your location, even when the app is closed, to show offers when you arrive at a place.</string>INFO.PLIST<key>NSLocationWhenInUseUsageDescription</key> <string>We use your location to show offers for places near you.</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>We use your location, even when the app is closed, to show offers when you arrive at a place.</string> - 4Run the app and approve the phoneRun the app on your phone. A Sandbox install waits as pending until you approve it, and Bubbl logs its code natively: in Logcat under the tag
BubblSDK, or in the Xcode console, not the Dart console. In the dashboard, open Configuration › Test Devices and approve the phone with that code, or choose Add a test device and approve it as it appears.LOGCAT (TAG BUBBLSDK)Bubbl sandbox: approve this device with code K7Q-2MXXCODE CONSOLE (TECH.BUBBL.SDK)Bubbl sandbox: approve this device with code K7Q-2MXLOGCAT / XCODE CONSOLEBubbl sandbox: approve this device with code K7Q-2MXLOGCAT / XCODE CONSOLEBubbl sandbox: approve this device with code K7Q-2MX - 5Ask for permissionsAsk for notifications and location when it suits your app.
requestLocation(always: true)also asks for the second step ("Allow all the time" on Android, "Always" on iOS), which geofences need to work with the app closed. Bubbl shows its privacy view first when the dashboard's privacy setting says so.MAINACTIVITY.KT// From a coroutine, for example lifecycleScope.launch { … } after your onboarding screen val notifications = Bubbl.permissions.requestNotifications() // Android 13+; nothing to ask before that val location = Bubbl.permissions.requestLocation(always = true) // Or with a callback (also from Java) Bubbl.permissions.requestLocation(true) { status -> /* BubblPermissionStatus? */ }SWIFTlet notifications = await Bubbl.permissions.requestNotifications() let location = await Bubbl.permissions.requestLocation(always: true) // Or with a completion handler (called on the main thread) Bubbl.permissions.requestLocation(always: true) { status in /* BubblPermissionStatus? */ }DARTawait Bubbl.permissions.requestNotifications(); await Bubbl.permissions.requestLocation(always: true);TSawait Bubbl.permissions.requestNotifications(); await Bubbl.permissions.requestLocation({ always: true }); - 6Connect pushFor Android, upload your Firebase service account under Configuration › Firebase › Credentials, then upload
google-services.jsonunder Android apps (no Firebase files in your app) or keep it inandroid/appwith the Google services Gradle plugin. For iOS, add your app and APNs key under Configuration › Apple Push. See Push notifications for the details. - 7Check it worksRead
Bubbl.diagnostics()and check thatactiveandregisteredare true. OncehasPushTokenis true too, open the Active users card on the dashboard's home page, search for the phone'sinstallIdand choose Send test push.KOTLINBubbl.diagnostics { d -> Log.i("App", "installId=${d.installId} active=${d.active} registered=${d.registered} " + "push=${d.hasPushToken} geofences=${d.geofences} lastError=${d.lastError}") }SWIFTlet d = await Bubbl.diagnostics() print(d.installId ?? "none", d.active, d.registered, d.hasPushToken, d.geofences, d.lastError ?? "none")DARTfinal d = await Bubbl.diagnostics(); debugPrint('installId=${d.installId} active=${d.active} registered=${d.registered} ' 'push=${d.hasPushToken} geofences=${d.geofences} lastError=${d.lastError}');TSconst d = await Bubbl.diagnostics(); console.log(d.installId, d.active, d.registered, d.hasPushToken, d.geofences, d.lastError);
React Native#
These steps are for a React Native CLI app. For Expo, see Expo below.
- 1Install the packageInstall
@bubblsdk/react-native-sdkat the exact version shown (a plainnpm installstill gets 4.x while 5.0 is in pre-release), then runpod installfor iOS. It needs React Native 0.76 or later with the New Architecture on, the default from 0.76. The Bubbl iOS SDK is built into the package's pod, and Android needs nothing extra.BUILD.GRADLE.KTSandroid { compileSdk = 35 } dependencies { implementation("tech.bubbl.sdk:bubbl-sdk:5.0.0") }PACKAGEXcode › File › Add Package Dependencies… Package URL: https://github.com/bubbl-public/bubbl-ios-sdk Dependency rule: Exact Version 5.0.0 Product: BubblSDK (add it to your app target)PUBSPEC.YAMLdependencies: flutter: sdk: flutter bubbl_flutter_sdk: 5.0.0INSTALLnpm install @bubblsdk/react-native-sdk@5.0.0 cd ios && pod install - 2iOS: add location strings and the push capabilityAdd the two location usage strings to your app's
Info.plist, and the Push Notifications capability under Signing & Capabilities in Xcode.INFO.PLIST<key>NSLocationWhenInUseUsageDescription</key> <string>We use your location to show offers for places near you.</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>We use your location, even when the app is closed, to show offers when you arrive at a place.</string>INFO.PLIST<key>NSLocationWhenInUseUsageDescription</key> <string>We use your location to show offers for places near you.</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>We use your location, even when the app is closed, to show offers when you arrive at a place.</string>INFO.PLIST<key>NSLocationWhenInUseUsageDescription</key> <string>We use your location to show offers for places near you.</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>We use your location, even when the app is closed, to show offers when you arrive at a place.</string> - 3Start Bubbl in index.jsIn the dashboard, open Configuration › Developers, copy the SDK API key (a Sandbox key starts
pk_test_) and note the API address shown under it. CallBubbl.startwith both once, at the top ofindex.js. A mistake is logged natively, never thrown, and calls beforestartdo nothing. When the OS wakes your app for a geofence or a push, the native SDK starts itself without JavaScript.APP.KTimport android.app.Application import tech.bubbl.sdk.Bubbl import tech.bubbl.sdk.BubblOptions class App : Application() { override fun onCreate() { super.onCreate() Bubbl.start(this, "pk_test_…", BubblOptions(baseUrl = "https://api.sandbox.bubbl.tech")) } }ANDROIDMANIFEST.XML<application android:name=".App" ...>MYAPP.SWIFTimport BubblSDK import SwiftUI @main struct MyApp: App { init() { Bubbl.start(apiKey: "pk_test_…", options: BubblOptions(baseUrl: "https://api.sandbox.bubbl.tech")) } var body: some Scene { WindowGroup { ContentView() } } }APPDELEGATE.SWIFTimport BubblSDK import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Bubbl.start(apiKey: "pk_test_…", options: BubblOptions(baseUrl: "https://api.sandbox.bubbl.tech")) return true } }MAIN.DARTimport 'package:bubbl_flutter_sdk/bubbl_flutter_sdk.dart'; import 'package:flutter/widgets.dart'; Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await Bubbl.start( apiKey: 'pk_test_…', options: const BubblOptions(baseUrl: 'https://api.sandbox.bubbl.tech'), ); runApp(const MyApp()); }INDEX.JSimport { Bubbl } from '@bubblsdk/react-native-sdk'; Bubbl.start('pk_test_…', { baseUrl: 'https://api.sandbox.bubbl.tech' }); // …then your app's usual AppRegistry.registerComponent(…) - 4Run the app and approve the phoneRebuild and run the app on your phone. A Sandbox install waits as pending until you approve it, and Bubbl logs its code natively, in Logcat under the tag
BubblSDKor in the Xcode console. In the dashboard, open Configuration › Test Devices and approve the phone with that code, or choose Add a test device and approve it as it appears.LOGCAT (TAG BUBBLSDK)Bubbl sandbox: approve this device with code K7Q-2MXXCODE CONSOLE (TECH.BUBBL.SDK)Bubbl sandbox: approve this device with code K7Q-2MXLOGCAT / XCODE CONSOLEBubbl sandbox: approve this device with code K7Q-2MXLOGCAT / XCODE CONSOLEBubbl sandbox: approve this device with code K7Q-2MX - 5Ask for permissionsAsk for notifications and location when it suits your app.
requestLocation({ always: true })also asks for the second step ("Allow all the time" on Android, "Always" on iOS), which geofences need to work with the app closed. Bubbl shows its privacy view first when the dashboard's privacy setting says so.MAINACTIVITY.KT// From a coroutine, for example lifecycleScope.launch { … } after your onboarding screen val notifications = Bubbl.permissions.requestNotifications() // Android 13+; nothing to ask before that val location = Bubbl.permissions.requestLocation(always = true) // Or with a callback (also from Java) Bubbl.permissions.requestLocation(true) { status -> /* BubblPermissionStatus? */ }SWIFTlet notifications = await Bubbl.permissions.requestNotifications() let location = await Bubbl.permissions.requestLocation(always: true) // Or with a completion handler (called on the main thread) Bubbl.permissions.requestLocation(always: true) { status in /* BubblPermissionStatus? */ }DARTawait Bubbl.permissions.requestNotifications(); await Bubbl.permissions.requestLocation(always: true);TSawait Bubbl.permissions.requestNotifications(); await Bubbl.permissions.requestLocation({ always: true }); - 6Connect pushFor Android, upload your Firebase service account under Configuration › Firebase › Credentials, then upload
google-services.jsonunder Android apps (no Firebase files in your app) or keep it inandroid/appwith the Google services Gradle plugin. For iOS, add your app and APNs key under Configuration › Apple Push. See Push notifications for the details. - 7Check it worksRead
Bubbl.diagnostics()and check thatactiveandregisteredare true. OncehasPushTokenis true too, open the Active users card on the dashboard's home page, search for the phone'sinstallIdand choose Send test push.KOTLINBubbl.diagnostics { d -> Log.i("App", "installId=${d.installId} active=${d.active} registered=${d.registered} " + "push=${d.hasPushToken} geofences=${d.geofences} lastError=${d.lastError}") }SWIFTlet d = await Bubbl.diagnostics() print(d.installId ?? "none", d.active, d.registered, d.hasPushToken, d.geofences, d.lastError ?? "none")DARTfinal d = await Bubbl.diagnostics(); debugPrint('installId=${d.installId} active=${d.active} registered=${d.registered} ' 'push=${d.hasPushToken} geofences=${d.geofences} lastError=${d.lastError}');TSconst d = await Bubbl.diagnostics(); console.log(d.installId, d.active, d.registered, d.hasPushToken, d.geofences, d.lastError);
Expo#
Bubbl works in Expo SDK 52 or later, in a development build (npx expo prebuild or EAS Build) with the New Architecture on. Expo Go can't load Bubbl: there, and in your app's web build, Bubbl's calls do nothing and Bubbl.diagnostics() answers with started false and the reason in lastError. For iOS, Expo SDK 57 needs Xcode 26.4 or later.
npx expo install @bubblsdk/react-native-sdk@5.0.0{
"expo": {
"ios": { "bundleIdentifier": "com.example.myapp" },
"android": { "package": "com.example.myapp" },
"plugins": [
["@bubblsdk/react-native-sdk", {
"ios": {
"locationWhenInUse": "We use your location to show offers for places near you.",
"locationAlways": "We use your location, even when the app is closed, to show offers when you arrive."
}
}]
]
}
}import { Bubbl } from '@bubblsdk/react-native-sdk';
Bubbl.start('pk_test_…', { baseUrl: 'https://api.sandbox.bubbl.tech' });
export default function RootLayout() {
// …
}npx expo prebuild
npx expo run:android # or: npx expo run:ios- 1Install the packageInstall
@bubblsdk/react-native-sdkat the exact version shown withnpx expo install. - 2Add the config pluginSet
ios.bundleIdentifierandandroid.packageinapp.jsonand add Bubbl's config plugin. Its options are all optional:"plugins": ["@bubblsdk/react-native-sdk"]is enough. At prebuild it adds the push entitlement (aps-environment) and the two location usage strings (yours from the options, otherwise your app's own, otherwise Bubbl's defaults). - 3Start Bubbl once, earlyWith Expo Router, call
Bubbl.startat the top ofapp/_layout.tsx, outside the component; otherwise at the top of your entry file. Use the SDK API key and API address from Configuration › Developers. - 4Build a development buildRun
npx expo prebuild, thennpx expo run:androidornpx expo run:ios, or build with EAS Build. Then approve the phone, ask for permissions, connect push and check diagnostics as in the React Native steps above.
Next steps#
- Push notifications: the full push setup for each platform, including pictures on iOS pushes.
- Location and geofences: background location and what the Play Console asks for.
- Sandbox and Production: going live with your
pk_live_…key. - Adding Bubbl to an existing app: your own Firebase service, notification delegate or permission flow.
- Troubleshooting: if
registeredstays false or pushes don't arrive. - In the dashboard, Set up the SDK walks the same steps with a live checklist.