Skip to main content

Push Notifications with Expo

Overview: https://docs.expo.dev/push-notifications/overview/

Expo Package: https://docs.expo.dev/versions/latest/sdk/notifications/

Pushing Notifications using Expo API: https://docs.expo.dev/push-notifications/sending-notifications/

Ideally you would call registerForPushNotificationsAsync in an onboarding flow, not just immediately once the app opens.

NotificationProvider

The following is a Notification Context Provider which can be wrapped around the App component eg.

App.tsx
const App: any = () => {
return (
<NotificationProvider>
...
</NotificationProvider>
);
}
NotificationContext.tsx
import React, { createContext, useEffect, useRef, useState } from 'react'
import * as Notifications from 'expo-notifications'
import { Notification, NotificationContentAndroid, NotificationContentIos } from 'expo-notifications'
import { Platform } from 'react-native'
import { registerUserNotifications, updateUser } from '../query/UserQueries'
import { observer } from 'mobx-react'
import { PushNotificationTrigger } from 'expo-notifications/src/Notifications.types'
import { useStores } from '../../models/helpers/useStores'
import * as Application from 'expo-application'
import * as RootNavigation from '../navigation/RootNavigation'

const registerForPushNotificationsAsync = async () => {
try {
const { status: existingStatus } = await Notifications.getPermissionsAsync()
let finalStatus = existingStatus
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync()
finalStatus = status
}

if (finalStatus !== 'granted') {
// alert('Failed to get push token for push notification!');
return null
}

const getResponse = await Notifications.getExpoPushTokenAsync({
development: (__DEV__)
})
if (getResponse) {
const token: string = getResponse.data

if (Platform.OS === 'android') {
await Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C'
})
}
return token
}
return null
} catch (e) {
return null
}
}

export type NotificationContextData = {
expoPushToken?: string
notification?: Notification
}

export const NotificationContext = createContext<NotificationContextData>({} as NotificationContextData)

const useNotificationContext = () => React.useContext(NotificationContext)
const NotificationProvider: React.FC<any> = observer(({ children }) => {
const { authStore, uiStateStore } = useStores()

const [expoPushToken, setExpoPushToken] = useState<string>()
const [notification, setNotification] = useState<any>()
const notificationListener = useRef<any>()
const responseListener = useRef<any>()

Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: false,
shouldSetBadge: false
})
})

useEffect(() => {
let notificationsEnabled = authStore.user.allowPushNotifications

if (authStore.isSignedIn) {
registerForPushNotificationsAsync().then((token: string | null) => {
if (token !== null) {
notificationsEnabled = true
setExpoPushToken(token)
if (authStore.user.userId) {
registerUserNotifications({
applicationId: Application.applicationId ?? undefined,
userId: authStore.user.userId,
pushToken: token,
development: (__DEV__)
}).catch((e) => console.warn('registerUserNotifications', e))

updateUser({
allowPushNotifications: notificationsEnabled
}).catch((e) => console.warn('updateUser', e))
}
}
})

// This listener is fired whenever a notification is received while the app is foregrounded
notificationListener.current = Notifications.addNotificationReceivedListener((notification) => {
try {
const trigger: PushNotificationTrigger = notification.request.trigger as PushNotificationTrigger
console.tron.log!('addNotificationReceivedListener trigger', trigger)
if (trigger.type == 'push') {
if (Platform.OS === 'ios' || Platform.OS === 'android') {
const content: any = notification.request.content as NotificationContentIos | NotificationContentAndroid

// Increment the little red badge counter on the app icon
if (content && content.data && content.data.quote) {
uiStateStore.setQuoteBadge(content.badge ?? 0)
}
}

setNotification(notification)
}
} catch (e: any) {
console.tron.warn!({ message: 'addNotificationReceivedListener', exception: e })
}
})

// This listener is fired whenever a user taps on or interacts with a notification (works when app is foregrounded, backgrounded, or killed)
responseListener.current = Notifications.addNotificationResponseReceivedListener((response) => {
try {
const trigger: PushNotificationTrigger = response.notification.request.trigger as PushNotificationTrigger
console.tron.log!('addNotificationResponseReceivedListener trigger', trigger)
if (trigger.type == 'push') {
if (Platform.OS === 'ios' || Platform.OS === 'android') {
const content: any = response.notification.request.content as
| NotificationContentIos
| NotificationContentAndroid

if (content && content.data && content.data.peerTopFive) {
RootNavigation.navigateToLibrary('peers')
}

if (content && content.data && content.data.quote) {
console.tron.log!('show daily quote')
uiStateStore.showDailyQuote({
slug: undefined,
name: undefined,
message: content.data.quote.message,
author: content.data.quote.author
})
}
}
}
} catch (e: any) {
console.tron.warn!({ message: 'addNotificationResponseReceivedListener', exception: e })
}
})

return () => {
Notifications.removeNotificationSubscription(notificationListener.current)
Notifications.removeNotificationSubscription(responseListener.current)
}
} else {
console.tron.log!('not registering for notifications')
}

if (notificationsEnabled !== authStore.user.allowPushNotifications) {
updateUser({
allowPushNotifications: notificationsEnabled
}).catch((e) => console.tron.log!('updateUser', e))
}
}, [authStore.isSignedIn])

return <NotificationContext.Provider value={{ expoPushToken, notification }}>{children}</NotificationContext.Provider>
})

export { NotificationProvider, registerForPushNotificationsAsync, useNotificationContext }