Android Live Updates

A Live Update displays current data from your Android app in a push notification, home screen widget, or custom app view. AXP Android SDK 16.10+React Native Module 19.4+Capacitor Module 2.3+Flutter Module 7.9+

Live Updates overview

Android Live Updates bring functionality similar to iOS Live Activities to the Android platform, providing an easy way to display dynamically updated content.

They can make it easier to keep information updated in real time instead of receiving multiple notifications from the same app for things like a game’s latest score, food delivery status, or rideshare arrivals. They do not have time limits.

The Live Update is the container of the information, which can be displayed on a device in these formats:

Live Updates can be started, updated, and ended using push notifications or the SDK. The notification, widget, or custom app view refreshes with the information contained in the latest Live Update received by the device.

Implementing Live Updates

First you must implement a handler and define its type. This determines what occurs in the app when it receives a Live Update. Then register the handler with the Airship SDK so the handler can be notified upon receiving a live_update payload. Then start the Live Update by sending a push or implementing custom code. When you start the Live Update, you define its name in the live_update object. You can update and end by sending additional pushes or using the LiveUpdateManager.

  • Types must be unique for each LiveUpdateHandler defined by an app.
  • A handler can handle multiple uniquely named Live Updates.
  • A name can be unique for a Channel IDAn Airship-specific unique identifier used to address a channel instance, e.g., a smartphone, web browser, email address. (e.g., order-12345) or shared across multiple channels IDs (e.g., sports-game-123).
  • A name can be reused after its Live Update has ended.

For example, to provide updates for tracking a sports game, start a Live Update with name sports-game-123. The app will track and display changes through the Airship SDK using that name.

Creating a handler

The Airship SDK supports two types of Live Update handlers:

  • NotificationLiveUpdateHandler — Displays a notification with a custom layout, with content updated by the Live Update.
  • CustomLiveUpdateHandler — Receives Live Update events and provides flexibility to display content using a custom implementation. This can be used to power home screen widgets, views embedded in the app, and more.

Each handler type has two different interfaces that may be implemented, to support suspending or callback-based code:

  • SuspendLiveUpdateNotificationHandler
  • CallbackLiveUpdateNotificationHandler
  • SuspendLiveUpdateCustomHandler
  • CallbackLiveUpdateCustomHandler

The following SampleLiveUpdateHandler reads content from the Live Update payload and displays scores for a sports game in a custom notification layout, using RemoteViews:

class SampleLiveUpdateHandler : SuspendLiveUpdateNotificationHandler() {
    override suspend fun onUpdate(
        context: Context,
        event: LiveUpdateEvent,
        update: LiveUpdate
    ): LiveUpdateResult<NotificationCompat.Builder> {

        // Read content_state fields from the Live Update payload
        val teamOneScore = update.content.opt("team_one_score").getInt(0).toString()
        val teamTwoScore = update.content.opt("team_two_score").getInt(0).toString()
        val statusUpdate = update.content.opt("status_update").optString()

        // Expanded notification layout
        val bigLayout = RemoteViews(context.packageName, R.layout.sports_big).apply {
            setTextViewText(R.id.teamOneScore, teamOneScore)
            setTextViewText(R.id.teamTwoScore, teamTwoScore)
            setTextViewText(R.id.statusUpdate, statusUpdate)
        }

        // Collapsed notification layout
        val smallLayout = RemoteViews(context.packageName, R.layout.sports_small).apply {
            setTextViewText(R.id.teamOneScore, teamOneScore)
            setTextViewText(R.id.teamTwoScore, teamTwoScore)
        }

        // Create the notification builder
        val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_notification)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setCategory(NotificationCompat.CATEGORY_EVENT)
            .setStyle(NotificationCompat.DecoratedCustomViewStyle())
            .setCustomContentView(smallLayout)
            .setCustomBigContentView(bigLayout)

        // Return 'ok' with the notification builder.
        // The Airship SDK will handle posting the notification.
        // Returning LiveUpdateResult.cancel() will end the Live Update and dismiss the notification.
        return LiveUpdateResult.ok(builder)
    }

    companion object {
        private const val NOTIFICATION_CHANNEL_ID = "sports"
    }
}

Registering a handler

Handlers must be registered with LiveUpdateManager in order to receive Live Update events. This should be done once after takeOff.

Registering a handler

In your Autopilot class, or in Application.onCreate, register the types.

class SampleAutopilot : Autopilot() {

    override fun onAirshipReady(airship: UAirship) {

        LiveUpdateManager.shared().run {
            register(type = "notification", handler = SampleLiveUpdateHandler())
        }
    }
}

Using the AirshipPluginExtender, register the types.

@Keep
public final class AirshipExtender: AirshipPluginExtender {

    override fun onAirshipReady(context: Context, airship: UAirship) {
         LiveUpdateManager.shared().run {
            register(type = "notification", handler = SampleLiveUpdateHandler())
        }
    }

}

Using the AirshipPluginExtender, register the types.

@Keep
public final class AirshipExtender: AirshipPluginExtender {

    override fun onAirshipReady(context: Context, airship: UAirship) {
         LiveUpdateManager.shared().run {
            register(type = "notification", handler = SampleLiveUpdateHandler())
        }
    }

}

Using the AirshipPluginExtender, register the types.

@Keep
public final class AirshipExtender: AirshipPluginExtender {

    override fun onAirshipReady(context: Context, airship: UAirship) {
         LiveUpdateManager.shared().run {
            register(type = "notification", handler = SampleLiveUpdateHandler())
        }
    }

}
 Note

The type used above, "notification", is used to map Live Update events to the corresponding handler in your app. The value can be any string that is unique across all handlers registered by an app. This also allows a single handler to manage multiple Live Updates that each have a unique name.

Starting Live Updates

Once a handler has been registered, Live Updates can be started via the Push API by specifying a live_update payload. In this example, a Live Update will be started for all Android devices that have the tag "sports-scores":

{
    "device_types": [
        "android"
    ],
    "audience": {
        "tag": "sports-scores" 
    },
    "notification": {
        "android": {
            "live_update": {
                "name": "sports-game-123",
                "type": "notification",
                "event": "start",
                "content_state": {
                    "team_one_score": 0,
                    "team_two_score": 0,
                    "status_update": "Game started!"
                }
            }
        }
    }
}

Live Updates can also be started from within the app.

Starting a Live Update

LiveUpdateManager.shared().start(
    name = "sports-game-123",
    type = "notification", 
    content = jsonMapOf(
        "team_one_score" to 0,
        "team_two_score" to 0,
        "status_update" to "Game started!"
    )
)
Airship.android.liveUpdateManager.start({
  name: 'sports-game-123'
  type: 'notification',
  content: {
    team_one_score: 0,
    team_two_score: 0,
    status_update: 'Game started!'
  }
});
Airship.android.liveUpdateManager.start({
  name: 'sports-game-123'
  type: 'notification',
  content: {
    team_one_score: 0,
    team_two_score: 0,
    status_update: 'Game started!'
  }
});
if (Platform.isAndroid) {
  LiveUpdateStartRequest createRequest = LiveUpdateStartRequest(
    name: "sports-game-123",
    type: 'notification',
    content: {
        'team_one_score': 0,
        'team_two_score': 0,
        'status_update': 'Game started!'
    }
  );

  await Airship.liveUpdateManager.start(createRequest);
}

Updating Live Updates

Now that the Live Update is started, the content can be updated via the Push API with the following payload. This example will send an update to all Android channels that have a Live Update named sports-game-123. The audience field can be specified to restrict who receives the update.

{
    "device_types": [
        "android"
    ],
    "audience": "all",
    "notification": {
        "android": {
            "live_update": {
                "name": "sports-game-123",
                "event": "update",
                "content_state": {
                    "team_one_score": 3,
                    "team_two_score": 0
                }
            }
        }
    }
}

Live Updates can also be updated from within the app.

Updating a Live Update

LiveUpdateManager.shared().update(
    name = "sports-game-123",
    content = jsonMapOf(
        "team_one_score" to 3,
        "team_two_score" to 0,
        "status_update" to "Game started!"
    )
)
Airship.android.liveUpdateManager.update({
  name: 'sports-game-123'
  content: {
    team_one_score: 3,
    team_two_score: 0,
    status_update: 'Game started!'
  }
});
Airship.android.liveUpdateManager.update({
  name: 'sports-game-123'
  content: {
    team_one_score: 3,
    team_two_score: 0,
    status_update: 'Game started!'
  }
});
if (Platform.isAndroid) {
  List<LiveUpdate> updates = await Airship.liveUpdateManager.listAll();

  LiveUpdateUpdateRequest request = LiveUpdateUpdateRequest(
    name: "sports-game-123",
    content: {
        'team_one_score': 0,
        'team_two_score': 0,
        'status_update': 'Game started!'
    }
  );

  await Airship.liveUpdateManager.update(request);
}

Ending Live Updates

To end a Live Update via the Push API, send the following payload:

{
    "device_types": [
        "android"
    ],
    "audience": "all",
    "notification": {
        "android": {
            "live_update": {
                "name": "sports-game-123",
                "event": "end",
                "content_state": {
                    "team_one_score": 9,
                    "team_two_score": 6,
                    "status_update": "Game over!"
                }
            }
        }
    }
}

Ending a Live Update

LiveUpdateManager.shared().stop(
    name = "sports-game-123",
    content = jsonMapOf(
        "team_one_score" to 9,
        "team_two_score" to 6,
        "status_update" to "Game over!"
    )
)
Airship.android.liveUpdateManager.end({
  name: 'sports-game-123'
  content: {
    team_one_score: 9,
    team_two_score: 6,
    status_update: 'Game over!'
  }
});
Airship.android.liveUpdateManager.end({
  name: 'sports-game-123'
  content: {
    team_one_score: 9,
    team_two_score: 6,
    status_update: 'Game over!'
  }
});
if (Platform.isAndroid) {
  List<LiveUpdate> updates = await Airship.liveUpdateManager.listAll();

  LiveUpdateEndRequest stopRequest = LiveUpdateEndRequest(
    name: "sports-game-123"
  );

  await Airship.liveUpdateManager.end(stopRequest);
}

Clearing all active Live Updates

During development, it can be useful to reset Live Update tracking on app launch. This allows any Live Updates to be started fresh, even if they were already started during a previous launch. To end all currently active Live Updates, call the clearAll() method on LiveUpdateManager.

Clearing all Live Updates

LiveUpdateManager.shared().clearAll()
Airship.android.liveUpdateManager.clearAll();
Airship.android.liveUpdateManager.clearAll();
if (Platform.isAndroid) {
  await Airship.liveUpdateManager.clearAll();
}