โ ๏ธ WARNING
This library is in early development stage; breaking changes can be introduced in minor version upgrades.
Expo module that enables Android Live Updates functionality, allowing you to display real-time, ongoing notifications with progress tracking and dynamic content updates directly from your React Native app or Firebase Cloud.
- Live Notifications: Display persistent, ongoing notifications that stay visible until dismissed
- Progress Tracking: Show determinate or indeterminate progress bars in notifications
- Firebase Cloud Messaging integration: Manage Live Updates remotely via FCM push notifications
- Deep Linking: Navigate to specific app screens when users tap notifications
- Event Listeners: Track notification state changes (started, updated, stopped, dismissed, clicked)
Android Only: This library is currently available exclusively for Android. Live Updates functionality is supported starting from Android Baklava Preview (Android 16.0) SDK. Note that the standard Android 16.0 ("Baklava") SDK won't support Live Updates; you must use the Baklava Preview SDK. If Live Updates are not available on the device's SDK version, standard notifications will be displayed instead.
Looking for iOS? If you need similar functionality for iOS, check out expo-live-activity which provides Live Activities support for iOS 16.2+.
- Prepare Android emulator with
Android Baklava PreviewSDK. - Run
npm iin root &/exampledirectories. - Run
npm run android(ornpx expo run:android --deviceto select proper emulator) inexample/directory.
You can install this package from the repository:
npm install git+https://github.com/software-mansion-labs/expo-live-updates.gitOr if you have access to this repository locally:
npm install /path/to/expo-live-updatesUse the expo-live-updates plugin in your app config:
plugins: [
[
'expo-live-updates',
{
channelId: 'NotificationsChannelId',
channelName: 'Notifications Channel Name',
},
],
// ... other plugins
]Expo-live-updates require 2 Android permissions to work. Add them to android.permissions in app config and remember to request for them in React Native app.
permissions: [
'android.permission.POST_NOTIFICATIONS',
'android.permission.POST_PROMOTED_NOTIFICATIONS',
],Then prebuild your app with:
npx expo prebuild --cleanNow you can test Live Updates:
startLiveUpdate({ title: 'Test notifications' })-
startLiveUpdate(state: LiveUpdateState, config?: LiveUpdateConfig): number | undefinedCreates and displays a new Live Update notification. Returns notification ID or undefined if failed. -
updateLiveUpdate(notificationId: number, state: LiveUpdateState, config?: LiveUpdateConfig): voidUpdates an existing Live Update notification. -
stopLiveUpdate(notificationId: number): voidStops an existing Live Update notification.
addTokenChangeListener(listener: (event: TokenChangeEvent) => void): EventSubscription | undefinedSubscribes to FCM token changes. Returns current token (if it already exists) on start listening. Call.remove()to unsubscribe.
addNotificationStateChangeListener(listener: (event: NotificationStateChangeEvent) => void): EventSubscription | undefinedSubscribes to notification state changes (started, updated, stopped, dismissed, clicked). Call .remove() to unsubscribe
Defines the visual content and progress information for a Live Update notification:
type LiveUpdateState = {
title: string // Main title text
text?: string // Additional descriptive text
subText?: string // Subtext text
image?: LiveUpdateImage // Image data
icon?: LiveUpdateImage // Icon data
progress?: LiveUpdateProgress // Progress bar configuration
showTime?: boolean // Shows notification time
time?: number // Timestamp of notification time
shortCriticalText?: string // Critical text (max 7 chars recommended)
}
export type LiveUpdateImage = {
url: string // Local uri or url to image resource
isRemote: boolean // Wether a resource is local or remote
}
type LiveUpdateProgress = {
max?: number // Maximum progress value (default: 100)
progress?: number // Current progress value
indeterminate?: boolean // Whether to show indeterminate progress bar
points?: LiveUpdateProgressPoint[] // Points for dividing progress bar
segments?: LiveUpdateProgressSegment[] // Segments for dividing progress bar
}
type LiveUpdateProgressPoint = {
position: number // Point's position relative to progress bar length
color?: string // Point's color
}
type LiveUpdateProgressSegment = {
length: number // Segment's length
color?: string // Segment's color
}Configuration options for the Live Update notification. Separated from state to allow in the future updating only state without passing config every time:
type LiveUpdateConfig = {
backgroundColor?: string // Background color (only SDK < 16)
deepLinkUrl?: string // Deep link URL to navigate when tapped
}The LiveUpdateConfig supports a deepLinkUrl property that allows you to specify an in-app route to navigate to when the notification is clicked. If no deepLinkUrl is provided, the default behavior is to open the app.
- Define a scheme in your
app.config.ts:
export default {
scheme: 'myapp', // Your custom scheme
// ... other config
}- Handle deep links in React Native, f.e. with React Navigation:
const linking = {
prefixes: [prefix],
}
return <Navigation linking={linking} />- Create project at Firebase.
- Add android app to created project and download
google-services.json. To work with example app set package name toexpo.modules.liveupdates.exampleand skip other steps of Firebase instructions. - Place
google-services.jsonin/exampleapp or your app folder. - Set
android.googleServicesFilein app config to the path ofgoogle-services.jsonfile (like inexample/app.config.ts). This will inform module to init Firebase service. - Prebuild app with
npx expo prebuild --clean
Live Updates can be started, updated and stopped using FCM. To manage Live Update via FCM you need to send data message:
POST /v1/projects/<YOUR_PROJECT_ID>/messages:send
Host: fcm.googleapis.com
Authorization: Bearer <YOUR_BEARER_TOKEN>
{
"message":{
"token":"<DEVICE_PUSH_TOKEN>",
"data":{
"event":"update",
"notificationId":"1", // shouldn't be passed when event is set to 'start'
"title":"Firebase message",
"text":"This is a message sent via Firebase", // optional
"subText":"Firebase", // optional
"imageUrl":"", // optional
"iconUrl":"", // optional
"progressMax":"100", // optional: maximum progress value, if not provided = 100
"progressValue":"50", // optional: current progress value
"progressIndeterminate":"false", // optional: whether progress is indeterminate
"progressPoints":"[{\"position\":10,\"color\":\"red\"},{\"position\":50,\"color\":\"blue\"}]", // optional: should be a string with JSON
"progressSegments":"[{\"length\":50,\"color\":\"red\"},{\"length\":100,\"color\":\"blue\"}]", // optional: should be a string with JSON
"backgroundColor":"red", // optional, works only on SDK < Baklava
"shortCriticalText":"text", // optional: shouldn't be longer than 7 characters
"deepLinkUrl":"/Test", // optional: default it will just open the app
"showTime":"true", // optional: whether time is shown, if not provided = true
"time":"1761313668279" // optional: time as timestamp
}
}
}
Request variables:
<YOUR_PROJECT_ID>- can be found ingoogle-services.json- testing
<YOUR_BEARER_TOKEN>- can be generated using Google OAuth Playground <DEVICE_PUSH_TOKEN>- can be copied from the example app
There are some restrictions that should be followed while managing Live Updates via Firebase Cloud Messaging. Keep in mind that passing:
notificationIdwith event'start'is prohibited and will result in error. Notification id is generated on Live Update start and cannot be customized.iconUrlis not fully supported on API 36.1. On this version notification icon is your app icon and the only place where you will be able to see the difference is status bar.shortCriticalTextof length longer than 7 characters is not recommended. There is no guarantee how much text will be displayed if this limit is exceeded, based on Android documentation.progressIndeterminateastrue, the notification will show an indeterminate progress bar. Whenfalse, it will show a determinate progress bar with the current progress relative to the maximum value. All progress fields are optional. At leastprogressIndeterminate: trueorprogressValuemust be included for the progress to be displayed.subTextprovides information displayed in the notification, but there are no guarantees where exactly it will be located. Usually it is placed in the notification header.showTimeasfalse, the notification time will be hidden. Whentrue, the notification time will be displayed based on the provided timestamp fromtimeproperty. All time fields are optional. By default, the notification time is displayed with the time of its creation on the native side - for FCM Live Updates it will be the time of message delivery.timeaffects status chip content, but only when the given timestamp is at least 2 minutes in the future. Whentimeis passed together withshowCriticalText, onlyshowCriticalTextwill be displayed in status chip.progress.maxandprogress.segmentsat the same time will result in omittingprogress.maxvalue, because maximum value is based on provided segments.progressPointsmust be specific format. Convert your points of typeLiveUpdateProgressPoint[]to JSON and pass string with JSON asprogressPoints.progressSegmentsmust be specific format. Convert your segments of typeLiveUpdateProgressSegment[]to JSON and pass string with JSONprogressSegments.
Since 2012 Software Mansion is a software agency with experience in building web and mobile apps. We are Core React Native Contributors and experts in dealing with all kinds of React Native issues. We can help you build your next dream product โ Hire us.
Made by @software-mansion and
community ๐
