ChangelogEdit pageSTAFFOpen dashboard
Quickstart · iOS · Android · React Native · Flutter

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.

Pre-release API hostsUntil 5.0 is released, the Sandbox API is at 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#

  1. 1
    Install the SDK
    Check that mavenCentral() and google() are in your repositories in settings.gradle.kts, then add tech.bubbl.sdk:bubbl-sdk to 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.KTS
    android {
        compileSdk = 35
    }
    
    dependencies {
        implementation("tech.bubbl.sdk:bubbl-sdk:5.0.0")
    }
    PACKAGE
    Xcode › 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.YAML
    dependencies:
      flutter:
        sdk: flutter
      bubbl_flutter_sdk: 5.0.0
    INSTALL
    npm install @bubblsdk/react-native-sdk@5.0.0
    cd ios && pod install
  2. 2
    Start Bubbl in Application.onCreate
    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. Call Bubbl.start with both in your Application class's onCreate, and register the class in AndroidManifest.xml. Start it there, not in an activity: Android wakes your app in the background for geofences and pushes, and only Application.onCreate runs then.
    APP.KT
    import 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.SWIFT
    import 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.SWIFT
    import 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.DART
    import '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.JS
    import { Bubbl } from '@bubblsdk/react-native-sdk';
    
    Bubbl.start('pk_test_…', { baseUrl: 'https://api.sandbox.bubbl.tech' });
    
    // …then your app's usual AppRegistry.registerComponent(…)
  3. 3
    Run the app and approve the phone
    Run 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-2MX
    XCODE CONSOLE (TECH.BUBBL.SDK)
    Bubbl sandbox: approve this device with code K7Q-2MX
    LOGCAT / XCODE CONSOLE
    Bubbl sandbox: approve this device with code K7Q-2MX
    LOGCAT / XCODE CONSOLE
    Bubbl sandbox: approve this device with code K7Q-2MX
  4. 4
    Ask for permissions
    Ask 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? */ }
    SWIFT
    let 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? */ }
    DART
    await Bubbl.permissions.requestNotifications();
    await Bubbl.permissions.requestLocation(always: true);
    TS
    await Bubbl.permissions.requestNotifications();
    await Bubbl.permissions.requestLocation({ always: true });
  5. 5
    Connect push
    Upload your Firebase project's service account under Configuration › Firebase › Credentials. Then either keep your own google-services.json with the com.google.gms.google-services Gradle 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.
  6. 6
    Check it works
    Read Bubbl.diagnostics and check that active and registered are true. Once hasPushToken is true too, open the Active users card on the dashboard's home page, search for the phone's installId and choose Send test push. It arrives as a plain notification that opens your app.
    KOTLIN
    Bubbl.diagnostics { d ->
        Log.i("App", "installId=${d.installId} active=${d.active} registered=${d.registered} " +
            "push=${d.hasPushToken} geofences=${d.geofences} lastError=${d.lastError}")
    }
    SWIFT
    let d = await Bubbl.diagnostics()
    print(d.installId ?? "none", d.active, d.registered, d.hasPushToken, d.geofences, d.lastError ?? "none")
    DART
    final d = await Bubbl.diagnostics();
    debugPrint('installId=${d.installId} active=${d.active} registered=${d.registered} '
        'push=${d.hasPushToken} geofences=${d.geofences} lastError=${d.lastError}');
    TS
    const d = await Bubbl.diagnostics();
    console.log(d.installId, d.active, d.registered, d.hasPushToken, d.geofences, d.lastError);

iOS#

  1. 1
    Add the package
    In 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.KTS
    android {
        compileSdk = 35
    }
    
    dependencies {
        implementation("tech.bubbl.sdk:bubbl-sdk:5.0.0")
    }
    PACKAGE
    Xcode › 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.YAML
    dependencies:
      flutter:
        sdk: flutter
      bubbl_flutter_sdk: 5.0.0
    INSTALL
    npm install @bubblsdk/react-native-sdk@5.0.0
    cd ios && pod install
  2. 2
    Add location strings and the push capability
    Add 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>
  3. 3
    Start Bubbl at launch
    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. Call Bubbl.start with both in your SwiftUI App's init, or in application(_:didFinishLaunchingWithOptions:) in UIKit. Bubbl never crashes your app: a mistake such as an empty key or an http:// address is logged, not thrown.
    APP.KT
    import 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.SWIFT
    import 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.SWIFT
    import 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.DART
    import '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.JS
    import { Bubbl } from '@bubblsdk/react-native-sdk';
    
    Bubbl.start('pk_test_…', { baseUrl: 'https://api.sandbox.bubbl.tech' });
    
    // …then your app's usual AppRegistry.registerComponent(…)
  4. 4
    Run the app and approve the phone
    Run 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-2MX
    XCODE CONSOLE (TECH.BUBBL.SDK)
    Bubbl sandbox: approve this device with code K7Q-2MX
    LOGCAT / XCODE CONSOLE
    Bubbl sandbox: approve this device with code K7Q-2MX
    LOGCAT / XCODE CONSOLE
    Bubbl sandbox: approve this device with code K7Q-2MX
  5. 5
    Ask for permissions
    Ask 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 call registerForRemoteNotifications() 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? */ }
    SWIFT
    let 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? */ }
    DART
    await Bubbl.permissions.requestNotifications();
    await Bubbl.permissions.requestLocation(always: true);
    TS
    await Bubbl.permissions.requestNotifications();
    await Bubbl.permissions.requestLocation({ always: true });
  6. 6
    Connect push
    Under 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.
  7. 7
    Check it works
    Read Bubbl.diagnostics() and check that active and registered are true. Once hasPushToken is true too, open the Active users card on the dashboard's home page, search for the phone's installId and choose Send test push. A development-signed Xcode build gets it through Apple's sandbox environment.
    KOTLIN
    Bubbl.diagnostics { d ->
        Log.i("App", "installId=${d.installId} active=${d.active} registered=${d.registered} " +
            "push=${d.hasPushToken} geofences=${d.geofences} lastError=${d.lastError}")
    }
    SWIFT
    let d = await Bubbl.diagnostics()
    print(d.installId ?? "none", d.active, d.registered, d.hasPushToken, d.geofences, d.lastError ?? "none")
    DART
    final d = await Bubbl.diagnostics();
    debugPrint('installId=${d.installId} active=${d.active} registered=${d.registered} '
        'push=${d.hasPushToken} geofences=${d.geofences} lastError=${d.lastError}');
    TS
    const d = await Bubbl.diagnostics();
    console.log(d.installId, d.active, d.registered, d.hasPushToken, d.geofences, d.lastError);

Flutter#

  1. 1
    Add the package
    Add bubbl_flutter_sdk to pubspec.yaml with the exact version shown, then run flutter pub get. Name the version: SDK 5 is a pre-release, so a plain flutter pub add bubbl_flutter_sdk picks 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.KTS
    android {
        compileSdk = 35
    }
    
    dependencies {
        implementation("tech.bubbl.sdk:bubbl-sdk:5.0.0")
    }
    PACKAGE
    Xcode › 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.YAML
    dependencies:
      flutter:
        sdk: flutter
      bubbl_flutter_sdk: 5.0.0
    INSTALL
    npm install @bubblsdk/react-native-sdk@5.0.0
    cd ios && pod install
  2. 2
    Start 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. Call Bubbl.start with both in main(), before runApp. 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.KT
    import 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.SWIFT
    import 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.SWIFT
    import 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.DART
    import '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.JS
    import { Bubbl } from '@bubblsdk/react-native-sdk';
    
    Bubbl.start('pk_test_…', { baseUrl: 'https://api.sandbox.bubbl.tech' });
    
    // …then your app's usual AppRegistry.registerComponent(…)
  3. 3
    iOS: add location strings and the push capability
    Add 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>
  4. 4
    Run the app and approve the phone
    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 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-2MX
    XCODE CONSOLE (TECH.BUBBL.SDK)
    Bubbl sandbox: approve this device with code K7Q-2MX
    LOGCAT / XCODE CONSOLE
    Bubbl sandbox: approve this device with code K7Q-2MX
    LOGCAT / XCODE CONSOLE
    Bubbl sandbox: approve this device with code K7Q-2MX
  5. 5
    Ask for permissions
    Ask 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? */ }
    SWIFT
    let 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? */ }
    DART
    await Bubbl.permissions.requestNotifications();
    await Bubbl.permissions.requestLocation(always: true);
    TS
    await Bubbl.permissions.requestNotifications();
    await Bubbl.permissions.requestLocation({ always: true });
  6. 6
    Connect push
    For Android, upload your Firebase service account under Configuration › Firebase › Credentials, then upload google-services.json under Android apps (no Firebase files in your app) or keep it in android/app with the Google services Gradle plugin. For iOS, add your app and APNs key under Configuration › Apple Push. See Push notifications for the details.
  7. 7
    Check it works
    Read Bubbl.diagnostics() and check that active and registered are true. Once hasPushToken is true too, open the Active users card on the dashboard's home page, search for the phone's installId and choose Send test push.
    KOTLIN
    Bubbl.diagnostics { d ->
        Log.i("App", "installId=${d.installId} active=${d.active} registered=${d.registered} " +
            "push=${d.hasPushToken} geofences=${d.geofences} lastError=${d.lastError}")
    }
    SWIFT
    let d = await Bubbl.diagnostics()
    print(d.installId ?? "none", d.active, d.registered, d.hasPushToken, d.geofences, d.lastError ?? "none")
    DART
    final d = await Bubbl.diagnostics();
    debugPrint('installId=${d.installId} active=${d.active} registered=${d.registered} '
        'push=${d.hasPushToken} geofences=${d.geofences} lastError=${d.lastError}');
    TS
    const 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.

  1. 1
    Install the package
    Install @bubblsdk/react-native-sdk at the exact version shown (a plain npm install still gets 4.x while 5.0 is in pre-release), then run pod install for 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.KTS
    android {
        compileSdk = 35
    }
    
    dependencies {
        implementation("tech.bubbl.sdk:bubbl-sdk:5.0.0")
    }
    PACKAGE
    Xcode › 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.YAML
    dependencies:
      flutter:
        sdk: flutter
      bubbl_flutter_sdk: 5.0.0
    INSTALL
    npm install @bubblsdk/react-native-sdk@5.0.0
    cd ios && pod install
  2. 2
    iOS: add location strings and the push capability
    Add 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>
  3. 3
    Start Bubbl in index.js
    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. Call Bubbl.start with both once, at the top of index.js. A mistake is logged natively, never thrown, and calls before start do nothing. When the OS wakes your app for a geofence or a push, the native SDK starts itself without JavaScript.
    APP.KT
    import 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.SWIFT
    import 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.SWIFT
    import 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.DART
    import '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.JS
    import { Bubbl } from '@bubblsdk/react-native-sdk';
    
    Bubbl.start('pk_test_…', { baseUrl: 'https://api.sandbox.bubbl.tech' });
    
    // …then your app's usual AppRegistry.registerComponent(…)
  4. 4
    Run the app and approve the phone
    Rebuild 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 BubblSDK or 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-2MX
    XCODE CONSOLE (TECH.BUBBL.SDK)
    Bubbl sandbox: approve this device with code K7Q-2MX
    LOGCAT / XCODE CONSOLE
    Bubbl sandbox: approve this device with code K7Q-2MX
    LOGCAT / XCODE CONSOLE
    Bubbl sandbox: approve this device with code K7Q-2MX
  5. 5
    Ask for permissions
    Ask 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? */ }
    SWIFT
    let 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? */ }
    DART
    await Bubbl.permissions.requestNotifications();
    await Bubbl.permissions.requestLocation(always: true);
    TS
    await Bubbl.permissions.requestNotifications();
    await Bubbl.permissions.requestLocation({ always: true });
  6. 6
    Connect push
    For Android, upload your Firebase service account under Configuration › Firebase › Credentials, then upload google-services.json under Android apps (no Firebase files in your app) or keep it in android/app with the Google services Gradle plugin. For iOS, add your app and APNs key under Configuration › Apple Push. See Push notifications for the details.
  7. 7
    Check it works
    Read Bubbl.diagnostics() and check that active and registered are true. Once hasPushToken is true too, open the Active users card on the dashboard's home page, search for the phone's installId and choose Send test push.
    KOTLIN
    Bubbl.diagnostics { d ->
        Log.i("App", "installId=${d.installId} active=${d.active} registered=${d.registered} " +
            "push=${d.hasPushToken} geofences=${d.geofences} lastError=${d.lastError}")
    }
    SWIFT
    let d = await Bubbl.diagnostics()
    print(d.installId ?? "none", d.active, d.registered, d.hasPushToken, d.geofences, d.lastError ?? "none")
    DART
    final d = await Bubbl.diagnostics();
    debugPrint('installId=${d.installId} active=${d.active} registered=${d.registered} '
        'push=${d.hasPushToken} geofences=${d.geofences} lastError=${d.lastError}');
    TS
    const 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.

INSTALL
npx expo install @bubblsdk/react-native-sdk@5.0.0
APP.JSON
{
  "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."
        }
      }]
    ]
  }
}
_LAYOUT.TSX
import { Bubbl } from '@bubblsdk/react-native-sdk';

Bubbl.start('pk_test_…', { baseUrl: 'https://api.sandbox.bubbl.tech' });

export default function RootLayout() {
  // …
}
BUILD
npx expo prebuild
npx expo run:android   # or: npx expo run:ios
  1. 1
    Install the package
    Install @bubblsdk/react-native-sdk at the exact version shown with npx expo install.
  2. 2
    Add the config plugin
    Set ios.bundleIdentifier and android.package in app.json and 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).
  3. 3
    Start Bubbl once, early
    With Expo Router, call Bubbl.start at the top of app/_layout.tsx, outside the component; otherwise at the top of your entry file. Use the SDK API key and API address from Configuration › Developers.
  4. 4
    Build a development build
    Run npx expo prebuild, then npx expo run:android or npx 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#