Android live notifications

Create and update dynamic notifications on Android Mobile Devices

OneSignal's Android Live Notifications simulates iOS Live Activities for Android devices, delivering dynamic content through notifications to enhance user engagement and app interactivity. To receive Live Notifications, Android users must have push notifications enabled.

Requirements

  • Your Android project is integrated with the latest version of the OneSignal SDK.
  • Android users must have push notification permissions enabled.

Comparing push notifications and live notifications

Live Notifications allow you to display the most up-to-date information from your app in a dynamic notification for an event. Instead of getting multiple notifications, users get one notification with the latest changes over a set time updated through the same Create Message API to send push notifications.

Configuration

1. Configure Android Notification Service Extension

Create a new Notification Service Extension that implements INotificationServiceExtension interface from OneSignal Android SDK. Within this class, override methods to intercept and alter notifications before they are displayed to the user.

📘

Refer to Android Notification Service Extension for more details.

package com.onesignal.sample.android

import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import com.onesignal.notifications.INotificationReceivedEvent
import com.onesignal.notifications.INotificationServiceExtension
import org.json.JSONObject
import java.util.logging.Logger

class NotificationServiceExtension : INotificationServiceExtension {
  override fun onNotificationReceived(event: INotificationReceivedEvent) {
        val context = event.context
        val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
            ?: run {
                logger.warning("NotificationManager not available.")
                return
            }

        notificationManager.let {
            if (!notificationChannelsCreated) {
                createNotificationChannels(notificationManager)
            }
        }

        // Use `additional_data`to submit the Live Notification payload.
    		val additionalData = event.notification.additionalData
        val liveNotificationPayload = additionalData?.optJSONObject("live_notification")
        if (liveNotificationPayload == null) {
            logger.info("No live notification payload found. Showing original notification.")
            return
        }

        event.preventDefault()
        handleLiveNotification(event, liveNotificationPayload, notificationManager, context)
    }
}

2. Modify Android Manifest

Update the Android Manifest with the following property:

<meta-data android:name="com.onesignal.NotificationServiceExtension"
            android:value="com.onesignal.sample.android.NotificationServiceExtension" />

After updating, your manifest should look similar to the following code:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:name=".MainApplication"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.OneSignalAndroidSample"
        tools:targetApi="31">
        <meta-data android:name="com.onesignal.NotificationServiceExtension"
            android:value="com.onesignal.sample.android.NotificationServiceExtension" />
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

3. Create Live Notification Types

A Live Notification Type indicates which Live Notification to start.

Define notification identifiers

Notification identifiers indicate which notification to load and are used as keys in the Notification payload

private const val PROGRESS_LIVE_NOTIFICATION = "progress"

Create notification channels

Channels determine the visual and audio behaviors of the notification and must have an identifier. To learn more about notification channels, refer to Android's documentation.

private fun createNotificationChannels(notificationManager: NotificationManager) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (notificationManager.getNotificationChannel(PROGRESS_CHANNEL_ID) == null) {
                val progressChannel = NotificationChannel(
                    PROGRESS_CHANNEL_ID,
                    "Progress Live Notification",
                    NotificationManager.IMPORTANCE_LOW
                ).apply {
                    description = "Shows the progress of a download"
                }
                notificationManager.createNotificationChannel(progressChannel)
                notificationChannelsCreated = true
                logger.info("Notification channel created: $PROGRESS_CHANNEL_ID")
            }
        }
    }

Design a Live Notification

When designing a Live Notification, you have the flexibility to create a notification design for each update type. Each design you create must be assigned a specific type, allowing for varied presentations of a Live Notification.

private fun updateProgressNotification(
        liveNotificationUpdates: JSONObject,
        context: Context,
        notificationManager: NotificationManager
    ) {
        val currentProgress = liveNotificationUpdates.optInt("current_progress", 0)

        val builder = NotificationCompat.Builder(context, PROGRESS_CHANNEL_ID)
            .setContentTitle("Progress Live Notifications")
            .setContentText("It's working...")
            .setSmallIcon(android.R.drawable.ic_media_play)
            .setLargeIcon(BitmapFactory.decodeResource(context.resources, android.R.drawable.ic_dialog_info))
            .setOngoing(true)
            .setOnlyAlertOnce(true)
            .setProgress(100, currentProgress, false)

        notificationManager.notify(keyMap[PROGRESS_LIVE_NOTIFICATION]!!, builder.build())
        logger.info("Updated progress notification with progress: $currentProgress")
    }

It's important to consider:

  • Duration Live Notification will live in the notification center
  • Outcomes you're trying to achieve and what information is most important to display
  • Simple in design so users are not overwhelmed

A Live Notification can have:

  • Small icon & accent color
  • Large icon & accent color
  • App name
  • Big picture
  • Notification sound
  • Action buttons

📘

Android provides instructions to create a custom notification layout.

4. Extract the Live Notification payload

The additional_data property is optional, so build accordingly. If you send a regular push, nothing will happen because there is no Live Notification payload.

val additionalData = event.notification.additionalData
val liveNotificationPayload = additionalData?.optJSONObject("live_notification")

Live Notification Schema

PropertyRequiredDescription
keyYesUsed to load the correct notification UI.
eventYesThe action to perform on the Live Notification.
event_attributesNoStatic data is used to initialize the Live Notification; a Self-defined schema that defines the data your notification needs.
event_updatesNoDynamic content of the Live Notification. Must conform to the ContentState interface defined within your app's Live Notification.

Example payload

{
    "key": "celtics-vs-lakers",
    "event": "start",
    "event_attributes": {
        "homeTeam": "Celtics",
        "awayTeam": "Lakers",
        "game": "Finals Game 1"
    },
    "event_updates": {
        "quarter": 1,
        "homeScore": 0,
        "awayScore": 0,
    }
}

5. Respond to Live Notification events

Obtain the Live Notification event type from payload

val liveNotificationEvent = liveNotificationPayload.optString("event", "")

Event types

There are three events that you must handle when implementing Live Notifications: start, update, and end. The event your request contains determines which properties must be included in your request.

EventDescriptionRequired parameters
startStart the Live Notification with static and dynamic content that you want to display.event_attributes, event_updates
updateUpdate the Live Notification with new dynamic content.event_updates
endEnd the Live Notification.None

Usage

This is the Live Notification schema for our example.

{
  "live_notification": {
    "key": "string",
    "event": "start | update | end",
    "event_attributes": {
      "widget_attribute": "string" 
    },
    "event_updates": {
      "widget_content_state": "string"
    }
  }
}

Start Live Notification

This is the first step in sending Live Notifications to your customers.

  • Setevent_attributes to initialize the static data for the Live Notification with. This data will not change during the lifetime of the Live Notification.
  • Set event_updates data to initialize the dynamic data for the Live Notification. This is the data that can and will change during the lifetime of the Live Notification.
  • A collapse_iddetermines which notification to update and enables us to emulate a "Live Activity" by updating the same notification instead of showing a new notification. Set a collapse ID that is unique to this Live Notification to ensure subsequent updates are reflected in the same notification.

Example cURL request

curl -X "POST" "https://api.onesignal.com/notifications" \
     -H 'Authorization: BASIC <OS_REST_API_KEY>' \
     -H 'Content-Type: application/json; charset=utf-8' \
     -d $'{
  "app_id": "<APP_ID>",
  "target_channel": "push",
  "isAndroid": true,
  "collapse_id": "13"
  "data": {
    "live_notification": {
      "key": "progress",
      "event": "start",
      "event_attributes": {},
      "event_updates": {
        "current_progress": 0
      }
    }
  },
  "headings": {
    "en": "Start"
  },
  "contents": {
    "en": "Starting Live Notification"
  },
  "include_aliases": {
    "external_id": ["EID1", "EID2"]
  }
}'

Update Live Notification

You can update a Live Notification as many times as you like, so long as it's started first.

  • Set event_updates data to initialize the dynamic data for the Live Notification. This is the data that can and will change during the lifetime of the Live Notification and informs what to update your Live Notification's content with.

Example cURL request

curl -X "POST" "https://api.onesignal.com/notifications" \
     -H 'Authorization: BASIC <OS_REST_API_KEY>' \
     -H 'Content-Type: application/json; charset=utf-8' \
     -d $'{
  "app_id": "<APP_ID>",
  "target_channel": "push",
  "isAndroid": true,
  "collapse_id": "13"
  "data": {
    "live_notification": {
      "key": "progress",
      "event": "update",
			"event_attributes": {},
      "event_updates": {
        "current_progress": 80
      }
    }
  },
  "headings": {
    "en": "Update"
  },
  "contents": {
    "en": "Updating Live Notification"
  },
  "include_aliases": {
    "external_id": ["EID1", "EID2"]
  }
}'

End Live Notification

Example cURL request

curl -X "POST" "https://api.onesignal.com/notifications" \
     -H 'Authorization: Basic <OS_REST_API_KEY>' \
     -H 'Content-Type: application/json; charset=utf-8' \
     -d $'{
  "app_id": "<APP_ID>",
  "target_channel": "push",
  "isAndroid": true,
  "collapse_id": "13"
  "data": {
    "live_notification": {
      "key": "progress",
      "event": "dismiss"
    }
  },
  "headings": {
    "en": "Dismissing"
  },
  "contents": {
    "en": "Dismissing Live Notification"
  },
  "include_aliases": {
    "external_id": ["EID1", "EID2"]
  }
}'

Recommended

Prevent Auto-dismissal of Notifications

To keep a notification visible even after a user taps on it, call builder.setAutoCancel(false) on your notification builder. This prevents the notification from being automatically dismissed, allowing it to stay in the notification tray until explicitly removed by the user or the app.