ChangelogEdit pageSTAFFOpen dashboard

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.

  1. 1
    Set a notification listener
    Call setNotificationListener early, for example right after start. The listener is called on the main thread with a BubblMessage for 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.
    KOTLIN
    Bubbl.setNotificationListener { message ->
        if (message.isSurvey) return@setNotificationListener false   // let Bubbl show surveys
        showMyCard(message)
        true
    }
    SWIFT
    Bubbl.setNotificationListener { message in
        if message.isSurvey { return false }   // let Bubbl show surveys
        showMyCard(message)
        return true
    }
    DART
    await Bubbl.setNotificationListener((message) async {
      if (message.isSurvey) return false; // let Bubbl show surveys
      showMyCard(message);
      return true;
    });
    TS
    Bubbl.setNotificationListener((message) => {
      if (message.isSurvey) return false; // let Bubbl show surveys
      showMyBanner(message);
      return true;
    });
  2. 2
    Draw it and report what happens
    When your UI is on screen, call reportDisplayed. Then report what the user does: reportOpened when they open it from something of yours, reportCtaClicked for the call to action, reportDismissed when they close it without acting, reportMediaViewed and reportMediaCompleted for media, and reportSurveyStarted when they start a survey. Bubbl records these for your reports, as its own screen would.
    KOTLIN
    Bubbl.reportDisplayed(message)
    // …then, as they happen:
    Bubbl.reportDismissed(message)
    Bubbl.reportMediaViewed(message)
    Bubbl.reportMediaCompleted(message, durationSeconds = 42.0)
    SWIFT
    Bubbl.reportDisplayed(message)
    // …then, as they happen:
    Bubbl.reportDismissed(message)
    Bubbl.reportMediaViewed(message)
    Bubbl.reportMediaCompleted(message, durationSeconds: 42)
    DART
    await Bubbl.reportDisplayed(message);
    // …then, as they happen:
    await Bubbl.reportDismissed(message);
    await Bubbl.reportMediaViewed(message);
    await Bubbl.reportMediaCompleted(message, 42);
    TS
    Bubbl.reportDisplayed(message);
    // …then, as they happen:
    Bubbl.reportDismissed(message);
    Bubbl.reportMediaViewed(message);
    Bubbl.reportMediaCompleted(message, 42);
  3. 3
    Send survey answers
    For a survey, draw questions and send the answers with submitSurvey, 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.
    KOTLIN
    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
    ))
    SWIFT
    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
    ])
    DART
    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
    });
    TS
    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
    });
  4. 4
    Open the call to action
    openCta opens the message's call to action the way Bubbl's screen would, and reports the click, so you don't call reportCtaClicked as well. To hand a message back to Bubbl's screen after all, call present.
    KOTLIN
    Bubbl.openCta(message)     // opens ctaUrl and reports the click
    Bubbl.present(message)     // or: show it in Bubbl's screen after all
    SWIFT
    Bubbl.openCta(message)
    Bubbl.present(message)
    DART
    await Bubbl.openCta(message);
    await Bubbl.present(message);
    TS
    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:

TypeAnswer
Single choiceA choice ID
Multiple choiceA list of choice IDs
RatingA whole number from 1 to 5
BooleanTrue or false
NumberA number
SliderA number from 0 to 10
Open-endedText, 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#

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.