Drawing notifications yourself
Take a notification before Bubbl shows it, draw it in your own UI, and report what the user does so your campaign reports stay complete.
By default Bubbl shows every notification in its own notification screen. A notification listener gets a say first: return true and Bubbl shows nothing, so your app draws the message itself; return false and Bubbl shows it as usual. Set the listener back to null to let Bubbl show everything again.
- 1Set a notification listenerCall
setNotificationListenerearly, for example right afterstart. The listener is called on the main thread with aBubblMessagefor each notification Bubbl is about to show. Return true for the ones you draw yourself. A listener that throws counts as false, so Bubbl shows the notification rather than losing it. - 2Draw it and report what happensWhen your UI is on screen, call
reportDisplayed. Then report what the user does:reportOpenedwhen they open it from something of yours,reportCtaClickedfor the call to action,reportDismissedwhen they close it without acting,reportMediaViewedandreportMediaCompletedfor media, andreportSurveyStartedwhen they start a survey. Bubbl records these for your reports, as its own screen would. - 3Send survey answersFor a survey, draw
questionsand send the answers withsubmitSurvey, keyed by question ID. The answers are checked as the server checks them. It returns false, and logs why, when they can't be sent: for example a required question left unanswered, or a rating of 6. - 4Open the call to action
openCtaopens the message's call to action the way Bubbl's screen would, and reports the click, so you don't callreportCtaClickedas well. To hand a message back to Bubbl's screen after all, callpresent.
Bubbl.setNotificationListener { message ->
if (message.isSurvey) return@setNotificationListener false // let Bubbl show surveys
showMyCard(message)
true
}Bubbl.setNotificationListener { message in
if message.isSurvey { return false } // let Bubbl show surveys
showMyCard(message)
return true
}await Bubbl.setNotificationListener((message) async {
if (message.isSurvey) return false; // let Bubbl show surveys
showMyCard(message);
return true;
});Bubbl.setNotificationListener((message) => {
if (message.isSurvey) return false; // let Bubbl show surveys
showMyBanner(message);
return true;
});Bubbl.reportDisplayed(message)
// …then, as they happen:
Bubbl.reportDismissed(message)
Bubbl.reportMediaViewed(message)
Bubbl.reportMediaCompleted(message, durationSeconds = 42.0)Bubbl.reportDisplayed(message)
// …then, as they happen:
Bubbl.reportDismissed(message)
Bubbl.reportMediaViewed(message)
Bubbl.reportMediaCompleted(message, durationSeconds: 42)await Bubbl.reportDisplayed(message);
// …then, as they happen:
await Bubbl.reportDismissed(message);
await Bubbl.reportMediaViewed(message);
await Bubbl.reportMediaCompleted(message, 42);Bubbl.reportDisplayed(message);
// …then, as they happen:
Bubbl.reportDismissed(message);
Bubbl.reportMediaViewed(message);
Bubbl.reportMediaCompleted(message, 42);Bubbl.reportSurveyStarted(message)
val sent = Bubbl.submitSurvey(message, mapOf(
"q1" to "choice-2", // SINGLE_CHOICE: a choice id
"q2" to listOf("c1", "c3"), // MULTIPLE_CHOICE: choice ids
"q3" to 4, // RATING: 1–5
"q4" to "Great service", // OPEN_ENDED: text
))Bubbl.reportSurveyStarted(message)
let sent = Bubbl.submitSurvey(message, answers: [
"q1": "choice-2", // .singleChoice: a choice id
"q2": ["c1", "c3"], // .multipleChoice: choice ids
"q3": 4, // .rating: 1–5
"q4": "Great service", // .openEnded: text
])await Bubbl.reportSurveyStarted(message);
final sent = await Bubbl.submitSurvey(message, {
'q1': 'choice-2', // singleChoice: a choice id
'q2': ['c1', 'c3'], // multipleChoice: choice ids
'q3': 4, // rating: 1–5
'q4': 'Great service', // openEnded: text
});Bubbl.reportSurveyStarted(message);
const sent = await Bubbl.submitSurvey(message, {
q1: 'choice-2', // singleChoice: a choice id
q2: ['c1', 'c3'], // multipleChoice: choice ids
q3: 4, // rating: 1–5
q4: 'Great service', // openEnded: text
});Bubbl.openCta(message) // opens ctaUrl and reports the click
Bubbl.present(message) // or: show it in Bubbl's screen after allBubbl.openCta(message)
Bubbl.present(message)await Bubbl.openCta(message);
await Bubbl.present(message);Bubbl.openCta(message);
Bubbl.present(message);Survey answers by question type#
Each BubblQuestion has an id, text, type, required and choices (each with an id and text). Answer by type:
| Type | Answer |
|---|---|
| Single choice | A choice ID |
| Multiple choice | A list of choice IDs |
| Rating | A whole number from 1 to 5 |
| Boolean | True or false |
| Number | A number |
| Slider | A number from 0 to 10 |
| Open-ended | Text, up to 2000 characters |
The type names follow each language: SINGLE_CHOICE in Kotlin, .singleChoice in Swift (the enum is BubblQuestion.Kind), singleChoice in Dart (BubblQuestionType) and 'singleChoice' in TypeScript.
When the listener is asked#
On Android and iOS, the listener is asked for every notification Bubbl is about to show itself: from a geofence, a pull, or (on Android) a push, including while your app is in the background. If you return true in the background, Bubbl posts no system notification either, so only take what you can show. On iOS, alert pushes are drawn by iOS without running your app, so they reach the listener only when the app first sees them, for example when the user taps one and it opens.
In Flutter, the listener is asked only while Dart can show something: on Android while your Flutter activity is in front, on iOS while the app is 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, for example after a hot restart until the listener is set again, is shown by Bubbl too, never dropped.
In React Native, JavaScript is asked only while the app is active. In the background, Bubbl posts its own system notification, whose tap opens Bubbl's screen. If JavaScript can't answer, say mid-reload, Bubbl shows the notification, so none is lost.
Sandbox messages#
Sandbox workspaces aren't open on production yet. This applies to a workspace that has one. Messages from a Sandbox workspace have isSandbox set. Bubbl's own screen shows a SANDBOX ribbon on them; mark them in your UI too, so a test message isn't mistaken for a live campaign.
Related#
- Notifications and surveys: what Bubbl's own screen does.
- BubblMessage
- Bubbl: every report function.