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:
- Push NotificationA message that can appear on any screen on a mobile device. Push notifications appear as banners. — API only. Format is determined by custom layout.
- Home screen widget
- Custom app view — The content from a Live Update can be presented using custom code.
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())
}
}
}
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();
}
Categories