โ ๏ธ 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 API 36.1. Note that the standard API 36.0 won't support Live Updates; you must use API 36.1. If Live Updates are not available, 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
API 36.1. - 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 | undefined: Creates and displays a new Live Update notification. Returns id of the created notification or undefined if creation failed. -
updateLiveUpdate(notificationId: number, state: LiveUpdateState, config?: LiveUpdateConfig): void: Updates an existing Live Update notification. -
stopLiveUpdate(notificationId: number): void: Stops an existing Live Update notification.
addTokenChangeListener(listener: (event: TokenChangeEvent) => void): EventSubscription | undefined: Subscribes to FCM token changes. Returns current token (if it already exists) on start listening. Call.remove()to unsubscribe.
addNotificationStateChangeListener(listener: (event: NotificationStateChangeEvent) => void): EventSubscription | undefined: Subscribes 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
text?: string
subText?: string
image?: LiveUpdateImage // { url: string, isRemote: boolean}
icon?: LiveUpdateImage // { url: string, isRemote: boolean}
progress?: LiveUpdateProgress
shortCriticalText?: string
showTime?: boolean
time?: number
}Important notes:
- Adding
iconresults in changing icon inside notification and device status bar. However, on API 36.1 only the icon inside status bar will be changed - icon inside notification is your app's icon and cannot be changed withiconproperty. shortCriticalTextis recommended to be not longer than 7 characters. If this limit is exceeded, there is no guarantee how much text will be displayed, based on Android documentation.subTextis displayed in the notification header area, but there is no guarantee where exactly it will be located, based on Android documentation.showTimeindicates wether notification time should be displayed. By default, time is always visible - if you want to hide it, you need to directly setshowTimetofalse.timeshould be a timestamp based on which time inside notification will be displayed. Time will be visible inside status chip as well, but only when the given timestamp is at least 2 minutes in the future andshortCriticalTextis not defined.- Notification time is based on timestamp provided with
timeproperty. Iftimeis undefined, the time of notification's creation (on the native side) is displayed - keep in mind that for Live Updates created with FCM it will be the time of Firebase message delivery.
Defines progress representation for a Live Update notification:
type LiveUpdateProgress = {
max?: number
progress?: number // default: 100
indeterminate?: boolean
points?: LiveUpdateProgressPoint[] // { position: number, color?: string }
segments?: LiveUpdateProgressSegment[] // { position: number, color?: string }
}Important notes:
- Progress will not be displayed unless
progressIndeterminateis set totrueorprogressis passed. - Progress maximum value is specified with
max. However, ifsegmentsproperty is defined, maximum value will be calculated based on providedsegmentsand themaxproperty will be ignored.
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 = {
deepLinkUrl?: string
iconBackgroundColor?: string
}Important notes:
- On API 36.1 adding
iconBackgroundColorwill have no effect, because icon inside notification is your app's icon and it's background cannot be changed withiconBackgroundColorproperty.
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} />Managing a Live Update:
const state: LiveUpdateState = {
title: "This is a title",
text: "This is a text",
progress: {
progress: 70,
segments: [
{ length: 50, color: "red" },
{ length: 100, color: "blue" },
],
points: [
{ position: 10, color: "red" },
{ position: 50, color: "blue" },
],
},
};
const config: LiveUpdateConfig = {
deepLinkUrl: "/dashboard",
};
const notificationId = LiveUpdates.startLiveUpdate(state, config);
// Store notificationId for future referenceThis will initiate a Live Update with the specified title, text and progress with points and segment.
Subscribing to notifications state changes:
useEffect(() => {
const notificationStateChangeSubscription =
LiveUpdates.addNotificationStateChangeListener(
({ notificationId, action, timestamp }) => {
// Handle notification state change
}
);
return () => {
notificationStateChangeSubscription?.remove();
};
}, []);- 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: if not provided = 100
"progressValue":"50", // optional
"progressIndeterminate":"false", // optional
"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
"iconBackgroundColor":"red", // optional
"shortCriticalText":"text", // optional: shouldn't be longer than 7 characters
"deepLinkUrl":"/Test", // optional: default it will just open the app
"showTime":"true", // optional: if not provided = true
"time":"1761313668279" // optional: should be 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.progressPointsmust be a string with specific format. Convert your points of typeLiveUpdateProgressPoint[]to JSON and pass it as string toprogressPoints.progressSegmentsmust be a string with specific format. Convert your segments of typeLiveUpdateProgressSegment[]to JSON and pass it string toprogressSegments.
Subscribing to push token changes:
useEffect(() => {
const tokenChangeSubscription = LiveUpdates.addTokenChangeListener(
({ token }) => {
// Handle push token change
}
);
return () => {
tokenChangeSubscription?.remove();
};
}, []);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 ๐
