bubbl_flutter_sdk 5.0.0-alpha.1
bubbl_flutter_sdk: ^5.0.0-alpha.1 copied to clipboard
Bubbl for Flutter - geofenced and scheduled notifications, surveys and analytics, bridging the native Bubbl SDKs.
Bubbl for Flutter #
Geofenced and scheduled notifications, surveys and analytics, as a thin bridge over the native
Bubbl SDKs. Everything (geofences, notifications, the event queue, consent) runs in the native
SDK, which keeps working with the app closed; Dart calls it and hears from it. The functions are
the native SDKs' own: see docs/PUBLIC_API.md.
5.0 pre-release, on Android and iOS alike.
Install #
Needs Flutter 3.32 or later. Installs on Android 6 (API 23) and iOS 13, and works on Android 8.1
(API 27) and iOS 17 and later; on older versions every call does nothing, safely, and
await Bubbl.isSupported is false. So a new app needs no minimum-version changes.
-
Add the package:
dependencies: bubbl_flutter_sdk: ^5.0.0-alpha.1or
flutter pub add bubbl_flutter_sdk:^5.0.0-alpha.1. 5.0 is a pre-release, so name the version: a plainflutter pub add bubbl_flutter_sdkpicks the latest stable release, 4.x, a different SDK.^5.0.0-alpha.1takes later 5.0 pre-releases and 5.0.0 itself as they come. -
Start Bubbl at launch:
import 'package:bubbl_flutter_sdk/bubbl_flutter_sdk.dart'; Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await Bubbl.start(apiKey: 'pk_live_…', options: const BubblOptions(baseUrl: 'https://…')); runApp(const MyApp()); } -
iOS:
- in
ios/Runner/Info.plist,NSLocationWhenInUseUsageDescriptionandNSLocationAlwaysAndWhenInUseUsageDescription(what the location prompts say); - the Push Notifications capability (Runner target › Signing & Capabilities), for Bubbl's pushes.
Swift Package Manager (Flutter's default) and CocoaPods both work: the plugin carries the Bubbl iOS SDK itself, so there's no separate pod or package to add.
- in
That's all. When the OS wakes the app for a geofence or a push, the native SDK starts itself from what it saved, before any Dart runs.
Before publishing to Google Play: the SDK declares background location
(ACCESS_BACKGROUND_LOCATION, for geofences with the app closed), so Play asks for the
location permissions declaration in the Play Console.
Background refresh (iOS, optional) #
Without it, Bubbl sends what's queued when the app goes to the background and on its next run.
With it, iOS wakes the app now and then and Bubbl sends it then. In ios/Runner/Info.plist:
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>tech.bubbl.sdk.refresh</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
</array>
(If the app already has either key, add the value to its array.) Bubbl registers the task itself at launch, before any Dart runs; with only one of the two, it doesn't.
Pictures on pushes (iOS, optional) #
iOS shows a push's picture only through a notification service extension in the app:
-
In Xcode, File › New › Target › Notification Service Extension (named
NotificationServicehere), with the same Minimum Deployments as the app. -
Replace the extension's
NotificationService.swiftwith:import BubblNotificationService class NotificationService: BubblNotificationService {} -
In
ios/Podfile, after theRunnertarget:target 'NotificationService' do use_frameworks! :linkage => :static pod 'BubblFlutterNotificationService', :path => '.symlinks/plugins/bubbl_flutter_sdk/ios' end(
use_frameworks!because Flutter's Runner target has it, and CocoaPods wants an extension and its app to agree; static, so there's nothing for the app to embed.)This works with Swift Package Manager on too: Flutter installs pods whenever the app has a Podfile. An app on Swift Package Manager with no
ios/Podfilegets Flutter's first:- turn Swift Package Manager off:
flutter config --no-enable-swift-package-manager; flutter build iosonce, which writesios/Podfile;- turn it back on:
flutter config --enable-swift-package-manager(the Podfile stays, and Flutter keeps runningpod installfor it); - add the
NotificationServicetarget block above to it.
- turn Swift Package Manager off:
-
In Xcode, the Runner target › Build Phases: drag "Embed Foundation Extensions" above "Thin Binary", or Xcode reports a dependency cycle.
examples/flutter-wrapper has this extension, built by CI both ways.
Bubbl's pushes then show their picture; anything else passes through untouched. An app with
its own extension work can override didReceive and call super for Bubbl's pushes only
(BubblNotificationService.isBubbl(request)).
Using it #
await Bubbl.permissions.requestLocation(always: true); // the privacy view first, if the dashboard says so
await Bubbl.setSegments(['vip']);
await Bubbl.track('purchase.completed', {'total': 12.5});
final subscription = Bubbl.addEventListener((event) {
if (event is BubblGeofenceEntered) print('Entered ${event.locationId}');
});
// Draw notifications yourself while the app is open (Bubbl still shows them in the background):
await Bubbl.setNotificationListener((message) async {
showMyCard(message); // then Bubbl.reportDisplayed(message), reportCtaClicked, submitSurvey…
return true; // false: Bubbl shows it
});
final diagnostics = await Bubbl.diagnostics();
Consent (requireConsent, setConsent, optOut, deleteMyData), setLocationEnabled,
present, the report* functions, submitSurvey, openCta and isBubblMessage are as in
docs/PUBLIC_API.md.
An app that gets its device credential elsewhere (installs provisioned ahead of time, its own pairing flow) starts with it instead of an API key, the same way at every launch; the device never registers itself:
await Bubbl.startWithCredential(
BubblCredential(keyId: keyId, secret: secret, installId: installId),
options: const BubblOptions(baseUrl: 'https://…'),
);
If the server refuses it, Bubbl stops, with a BubblCredentialRejected event and
credentialRejected in diagnostics, until it's started with a new one.
The one difference from the native SDKs: the notification listener is asked only while the app's Flutter screen is in front (Android: its Flutter activity; iOS: the app active). Otherwise Bubbl shows the notification itself: its own screen if the app is in front (behind a native screen or a permission dialog, say), a system notification if not. A notification Dart can't take (after a hot restart, until the listener is set again) is shown by Bubbl too, never dropped.
From 4.x #
5.0 is a new SDK against Bubbl's device API v1, not an update: there's no compatibility layer, and 4.x's data on the device isn't carried over (the install registers as new).
| 4.x | 5.0 |
|---|---|
BubblSdk.instance.boot(BubblConfig(apiKey, environment, runtimeBaseUrl, ingestBaseUrl, …)) |
Bubbl.start(apiKey:, options: BubblOptions(baseUrl:)): one base URL, no environments |
startLocationTracking, refreshGeofence, handleLocationUpdate, refresh, flush |
Gone: the SDK watches geofences, sends and refreshes by itself |
registerPushToken, handleFirebasePayload, handleNotificationPayload, showNotification |
Gone: the SDK takes the push token and handles Bubbl's pushes itself (isBubblMessage tells yours apart) |
setDefaultNotificationModalEnabled, modal styles, notificationRenderingMode |
setNotificationListener: return true to draw it yourself, then report*; present shows Bubbl's card |
updateSegments, track(BubblTrackEvent), submitSurveyResponse |
setSegments, track(name, properties), submitSurvey(message, answers) |
events (map-based) |
Bubbl.events / addEventListener: typed BubblEvents |
| (none) | Consent, permissions (with the dashboard's privacy view), setLocationEnabled, deleteMyData |
Android #
- minSdk 23 (Bubbl works from API 27; below it, calls do nothing). Depends on
tech.bubbl.sdk:bubbl-sdk(Maven Central). - The plugin applies the Kotlin Gradle plugin only when the app has no Kotlin of its own: AGP 9's built-in Kotlin, or the plugin Flutter 3.44+ applies, is used as it is. So it builds, without Flutter's warning about plugins applying KGP, on older and newer Flutter alike.
- Push: the SDK handles Bubbl's FCM pushes itself, alongside any Firebase messaging the app has.
Working on the plugin #
The iOS SDK's sources are copied into the plugin from renewed-sdk/ios (not kept twice in git).
Before building for iOS or publishing, from flutter/:
dart run tool/sync_ios_sources.dart
Then flutter analyze && flutter test, and the example in examples/flutter-wrapper.
An app can use a checkout instead of pub.dev: bubbl_flutter_sdk: { path: ../renewed-sdk/flutter },
with the sync above run once. If the native Android SDK's version isn't on Maven Central, build
against a local copy: ./gradlew publishToMavenLocal in renewed-sdk/android, and mavenLocal()
in the app's repositories (allprojects { repositories { … } } in android/build.gradle.kts, as
examples/flutter-wrapper does).