Android integration

Before you add the BV Garner SDK to your own app, use the BV Garner test app to confirm that your Firebase project, sender profile, and Play Integrity setup work together. After the test passes, integrate the SDK into your app.

This topic is for your Android developers. It is part of the setup described in Set up push notifications in Portal.

Before you begin

  1. You need a real Android phone (Android 11 or later) with Google Play or an emulator with a Google Play system image (Firebase Cloud Messaging needs Google Play services).
  2. Git, Android Studio, or IntelliJ IDEA with the Android plugin, JDK 17.
  3. Get the following from your Account Administrator or Technical Administrator:
    1. The App ID from the sender profile. See the Create a sender profile article, step 9. 
    2. The package name registered on the sender profile.
    3. The google-services.json file from the same Firebase project as the uploaded key.
    4. The Google Cloud project number linked for Play Integrity. See the Prepare for Android article, step 3.

    Verify your setup with the BV Garner test app

    The BV Garner test app runs the SDK with your Firebase project and your sender profile. Its tester screen shows the result of each step, so you can fix configuration issues before you write any integration code.

    Setup overview

    To set up the BV Garner test app for push notifications, complete these steps:

    1. Set up a test identity
    2. Get the code
    3. Configure the app
    4. Run the app
    5. Send a test notification

      Step 1: Set up a test identity

      Use a separate package name for the sample, for example com.yourcompany.shop.bvpushtest. Using your production package name would mean uploading the sample to your production app's Google Play listing.

      Step 2: Get the code

      Code
      git clone https://github.com/bazaarvoice/garner.git
      cd garner/test-app/android
      cp client.properties.example client.properties

      The app builds the SDK from source in the same repository. An SDK change appears the next time you run the app. Your own app takes the same artifact from Maven Central.

      Step 3: Configure the app

      XML
      appName=Shop Push Test
      packageName=com.yourcompany.shop.bvpushtest
      appId=10000001
      baseUrl=https://push.prod.bazaarvoice.com
      googleServicesJson=~/secure/bvpushtest/google-services.json
      cloudProjectNumber={Enter the noted number from Play Integrity}
      

      Edit client.properties:

      Key Value
      appName The name shown under the app icon (optional). The default is BV Garner.
      packageName The package name of your Firebase Android app and your sender profile. The default is bv.garner.test.app.
      appId The App ID from your sender profile.
      baseUrl The production endpoint (the default, https://push.prod.bazaarvoice.com), or the QA endpoint your Bazaarvoice contact gives you.
      googleServicesJson Path to your google-services.json.
      cloudProjectNumber Optional. The Google Cloud project number linked to your app in Google Play Console (App integrity > Play Integrity API), digits only. Without it, the SDK asks Play Integrity with no project number.

      Git ignores client.properties and the copied app/google-services.json. The build fails without a google-services.json.

      Step 4: Run the app

      1. Open test-app/android in Android Studio or IntelliJ IDEA and run the app configuration. Alternatively, run ./gradlew :app:installDebug from a terminal.
      2. Allow notifications when the app asks.

      The app has four tabs:

      Tabs What it shows
      Home Whether the device is ready. A banner (for example, "3 of 5 checks pass") and a checklist: device registered, push token sent, user signed in, notifications allowed, and quiet hours. Each problem has a button that fixes it or takes you to the fix.
      Account Sign in with a user ID and a customer email (both required). After you sign in, it shows the user and the session, plus Sign out. It always shows Receive pushes (opt in or opt out) and Quiet hours (Off, Default, or Set hours).
      Inbox Every push the SDK reported on this device, newest first, with its title, text, time, and outcome (shown, held, hidden and why, or tapped). Filter by All, Shown, Held, or Hidden. The device keeps the newest 500 until you select Clear. Select a push to copy its notification ID.
      Details Device ID, FCM token, App ID, package, endpoint, user ID, session, opt-out, quiet hours, and last error, each with Copy. It also has Copy all (JSON), Share, recent SDK events, and the raw inspector text.

      Step 5: Send a test notification

      1. Send a test push from the Portal: On Home, select Copy device ID and paste it into the Portal's Send test notification dialog. The push appears on the device and in Inbox. Selecting it opens the review page. A test push appears even when no user is signed in.
      2. Send a real push: On Account, enter a user ID and the customer email the push is addressed to, and then select Sign in. When Home shows a green check for User signed in, pushes for that email reach this device.
      3. Test at night: By default, pushes are held from 20:00 to 08:00 device time. On Account, set Quiet hours to Off, or use Set hours for your own window.

      You can also read the device ID in logcat: adb logcat -s BVPushDriver:I | grep DEVICEID.

      Caution
      Play Integrity rejects a locally built app on an emulator with ATTESTATION_REJECTED. To test, either use an endpoint that has attestation turned off, or install the app from a Google Play Console testing track on a real device.

      When every step on the tester screen passes, continue to Add the BV Garner SDK to your own app.

      Add the BV Garner SDK to your own app (Android)

      Setup overview

      To add the BV Garner SDK to your Android app, complete these steps:

      1. Add the dependency
      2. Check Firebase
      3. Initialize the SDK
      4. Add Play Integrity token
      5. Forward messages (if your app has its own messaging service)
      6. Link the signed-in customer
      7. Ask for notification permission (Android 13 and later)
      8. Respect customer preferences (optional)
      9. Exclude the SDK's files from Android Auto Backup
      10. Add a way to see the Device ID (internal builds)

      What you need

      1. minSdk 24 or higher.
      2. Firebase set up in the app.
      3. Kotlin 2.4.
      4. The SDK uses HTTPS only and connects to the production endpoint by default, so you don't need to set a URL.

      Step 1: Add the dependency

      In app/build.gradle.kts:

      Code
      dependencies {
          implementation("com.bazaarvoice:bv-push-sdk-android:1.1.0")
      }

      The artifact is on Maven Central (mavenCentral()).

      The SDK brings its own dependencies: Firebase Messaging, Play Integrity, OkHttp, and AndroidX Core, Browser, WorkManager, Lifecycle, and Startup. It's designed to add no more than 1.5 MB to a release build that uses R8.

      The SDK's manifest already declares its messaging service, the notification tap handler, the INTERNET, ACCESS_NETWORK_STATE, POST_NOTIFICATIONS, and RECEIVE_BOOT_COMPLETED permissions, and the browser queries. You don't need to add them.

      Step 2: Check Firebase

      • Put the google-services.json from your production Firebase project in app/, and apply the Google services Gradle plugin (id("com.google.gms.google-services")). Most apps that already use Firebase have both.
      • The package_name in google-services.json must equal your applicationId, including any suffixes.
      • The SDK fetches the FCM token itself. You don't need to pass it.

      Step 3: Initialize the SDK

      Call init once in Application.onCreate, with your App ID:

      Code
      import android.app.Application
      import com.bazaarvoice.push.BVPush
      import com.bazaarvoice.push.config.BVPushOptions
      
      class ShopApplication : Application() {
          override fun onCreate() {
              super.onCreate()
              // Replace 10000001 with the App ID of your sender profile.
              BVPush.init(this, 10000001)
          }
      }
      • init is lightweight on the main thread. Everything else runs in the background.
      • init never throws. If the App ID is invalid, the SDK stays inactive and emits an INVALID_APP_ID error event.
      • If you don't already have an Application class, register it in your manifest with android:name=".ShopApplication".

      If your app has no Application class, or it's a React Native or Expo app, let the SDK start itself from manifest metadata instead:

      XML
      <application>
        <meta-data
        android:name="com.bazaarvoice.push.APP_ID"
        android:value="10000001" />
      </application>

      Use one approach. If you use both, make sure the App ID is the same in each.

      Step 4: Add Play Integrity token

      Device registration carries a Play Integrity token. Give the SDK the Google Cloud project number linked to your app in Play Console (Test and release → App integrity → Play Integrity API) as a string resource: a plain 12-digit android:value is stored as a float and loses digits.

      XML
      <!-- res/values/strings.xml: <string name="bv_cloud_project_number">123456789012</string> -->
      <meta-data
      android:name="com.bazaarvoice.push.CLOUD_PROJECT_NUMBER"
      android:value="@string/bv_cloud_project_number" />
      

      Without it the SDK sends the request with no project number. A value that is not digits is ignored with a warning. Failures are written to logcat with Play's error code: adb logcat -s BVPush shows BVPush attestation failed code=\.

      Step 5: Forward messages if your app has its own messaging service

      If your app already extends FirebaseMessagingService, Android delivers each message to only one service. Forward Bazaarvoice messages and new tokens to the SDK:

      Code
      import com.bazaarvoice.push.BVFirebaseMessagingService
      import com.bazaarvoice.push.BVPush
      import com.google.firebase.messaging.FirebaseMessagingService
      import com.google.firebase.messaging.RemoteMessage
      
      class ShopMessagingService : FirebaseMessagingService() {
      override fun onMessageReceived(message: RemoteMessage) {
      if (BVFirebaseMessagingService.handleRemoteMessage(this, message.data)) return
      // Your existing handling for non-Bazaarvoice messages
      }
      
      override fun onNewToken(token: String) {
      BVPush.onNewToken(this, token)
      // Your existing token handling
      }
      }

      handleRemoteMessage returns true only for Bazaarvoice messages, so your other notifications keep working. If BVPush.diagnostics() reports fcmConflict=true, messages aren't reaching the SDK. Check this forwarding.

      Code
      // After the customer signs in. Calling it again for the same user is free (no network call).
      BVPush.linkIdentity(userId = customer.id, email = customer.email)
      
      // When the customer signs out
      BVPush.unlinkIdentity(customer.id)
      • Use the same email address that you send to Bazaarvoice in your purchase (transaction) data. Matching uses a hash of the normalized address, so a different address means the device never receives that customer's review requests.
      • userId is sent as is. Use your internal customer ID, not an email address or name.
      • Without a linked user, the SDK hides review requests on that device. Call linkIdentity on every sign-in, and on app start if the customer is already signed in.

      Step 7: Ask for notification permission (Android 13 and later)

      Code
      import android.Manifest
      import android.content.pm.PackageManager
      import android.os.Build
      import android.os.Bundle
      import androidx.activity.ComponentActivity
      import androidx.activity.result.contract.ActivityResultContracts
      import androidx.core.content.ContextCompat
      
      class MainActivity : ComponentActivity() {
      private val notificationPermission =
      registerForActivityResult(ActivityResultContracts.RequestPermission()) { _ -> }
      
      override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      if (Build.VERSION.SDK_INT >= 33 &&
      ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==
      PackageManager.PERMISSION_DENIED
      ) {
      notificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS)
      }
      }
      }
      
      

      If the customer denies permission or turns off the app's notifications, the SDK quietly suppresses review requests on that device and doesn't show an error.

      Step 8: Respect customer preferences (optional)

      Code
      BVPush.optOut()                    // Customer turned off review requests in your app's settings
      BVPush.optIn()                     // Customer turned them back on
      BVPush.setQuietHours(22, 0, 7, 0)  // Hold pushes from 22:00 to 07:00 device time
      BVPush.disableQuietHours()         // Show pushes at any time
      BVPush.resetQuietHours()           // Back to the default, 20:00 to 08:00

      Quiet hours use the device's local time. A push that arrives during quiet hours is held on the device and shown when quiet hours end. The default is 20:00–08:00.

      Step 9: Exclude the SDK's files from Android Auto Backup

      If your app allows backup, exclude the SDK's files. After a restore, the SDK discards restored credentials and registers again with a new Device ID, so excluding the files keeps backups clean. The file names include your App ID. The following example is for Android 12 and later (android:dataExtractionRules):

      XML
      <?xml version="1.0" encoding="utf-8"?>
      <data-extraction-rules>
        <cloud-backup>
          <exclude domain="sharedpref" path="bv_push_secure_10000001.xml" />
          <exclude domain="sharedpref" path="bv_push_settings_10000001.xml" />
        </cloud-backup>
        <device-transfer>
          <exclude domain="sharedpref" path="bv_push_secure_10000001.xml" />
          <exclude domain="sharedpref" path="bv_push_settings_10000001.xml" />
        </device-transfer>
      </data-extraction-rules>

      Step 10: Add a way to see the Device ID (internal builds)

      Testers need the Device ID to verify the sender profile. Show it on an internal or debug screen, or log it:

      Code
      import android.util.Log
      import com.bazaarvoice.push.BVPush
      
      fun logBvPushDetails() {
      val deviceId: String? = BVPush.deviceId              // null until registration finishes
      Log.i("ShopPush", "Bazaarvoice Device ID: $deviceId")
      Log.i("ShopPush", BVPush.diagnostics().toString())   // No secrets, so it's safe for support tickets
      }

      For more SDK detail in logcat, turn on debug logging in internal test builds only. The SDK redacts its logs.

      Code
      import com.bazaarvoice.push.log.Level
      import com.bazaarvoice.push.log.Logger
      
      Logger.level = Level.DEBUG

      You can also observe BVPush.state (a StateFlow with initialized, deviceId, and lastError) and BVPush.events, which reports each push as received, displayed, held, or suppressed with a reason. These events stay on the device and are never uploaded.

      Next step

      Verify your sender profile by sending a test notification, create a notification template in Review Channels, test it, and publish it to your site. For detailed instructions, refer to Verify and publish notification template.

      Provide feedback button