import * as Device from 'expo-device';
import { Subscription } from 'expo-modules-core';
import * as Notifications from 'expo-notifications';
import * as TaskManager from 'expo-task-manager';
import React from 'react';
import { Alert, Text, Platform, ScrollView, View } from 'react-native';
import registerForPushNotificationsAsync from '../api/registerForPushNotificationsAsync';
import HeadingText from '../components/HeadingText';
import ListButton from '../components/ListButton';
import MonoText from '../components/MonoText';
const BACKGROUND_NOTIFICATION_TASK = 'BACKGROUND-NOTIFICATION-TASK';
const BACKGROUND_TASK_SUCCESSFUL = 'Background task successfully ran!';
const BACKGROUND_TEST_INFO = `To test background notification handling:\n(1) Background the app.\n(2) Send a push notification from your terminal. The push token can be found in your logs, and the command to send a notification can be found at https://docs.expo.dev/push-notifications/sending-notifications/#http2-api. On iOS, you need to include "_contentAvailable": "true" in your payload.\n(3) After receiving the notification, check your terminal for:\n"${BACKGROUND_TASK_SUCCESSFUL}"`;
TaskManager.defineTask(BACKGROUND_NOTIFICATION_TASK, (_data) => {
console.log(BACKGROUND_TASK_SUCCESSFUL);
});
const remotePushSupported = Device.isDevice;
export default class NotificationScreen extends React.Component<
// See: https://github.com/expo/expo/pull/10229#discussion_r490961694
// eslint-disable-next-line @typescript-eslint/ban-types
{},
{
lastNotifications?: Notifications.Notification;
}
> {
static navigationOptions = {
title: 'Notifications',
};
private _onReceivedListener: Subscription | undefined;
private _onResponseReceivedListener: Subscription | undefined;
// See: https://github.com/expo/expo/pull/10229#discussion_r490961694
// eslint-disable-next-line @typescript-eslint/ban-types
constructor(props: {}) {
super(props);
this.state = {};
}
componentDidMount() {
if (Platform.OS !== 'web') {
this._onReceivedListener = Notifications.addNotificationReceivedListener(
this._handelReceivedNotification
);
this._onResponseReceivedListener = Notifications.addNotificationResponseReceivedListener(
this._handelNotificationResponseReceived
);
Notifications.registerTaskAsync(BACKGROUND_NOTIFICATION_TASK);
// Using the same category as in `registerForPushNotificationsAsync`
Notifications.setNotificationCategoryAsync('welcome', [
{
buttonTitle: `Don't open app`,
identifier: 'first-button',
options: {
opensAppToForeground: false,
},
},
{
buttonTitle: 'Respond with text',
identifier: 'second-button-with-text',
textInput: {
submitButtonTitle: 'Submit button',
placeholder: 'Placeholder text',
},
},
{
buttonTitle: 'Open app',
identifier: 'third-button',
options: {
opensAppToForeground: true,
},
},
])
.then((_category) => {})
.catch((error) => console.warn('Could not have set notification category', error));
}
}
componentWillUnmount() {
this._onReceivedListener?.remove();
this._onResponseReceivedListener?.remove();
}
render() {
return (
Local Notifications
Push Notifications
{!remotePushSupported && (
⚠️ Remote push notifications are not supported in the simulator, the following tests
should warn accordingly.
)}
Badge Number
Dismissing notifications
{this.state.lastNotifications && (
{JSON.stringify(this.state.lastNotifications, null, 2)}
)}
Notification Permissions
Notification triggers debugging
Notifications.getNextTriggerDateAsync({ seconds: 10 }).then((timestamp) =>
alert(new Date(timestamp!))
)
}
title="Get next date for time interval + 10 seconds"
/>
Notifications.getNextTriggerDateAsync({
hour: 9,
minute: 0,
repeats: true,
}).then((timestamp) => alert(new Date(timestamp!)))
}
title="Get next date for 9 AM"
/>
Notifications.getNextTriggerDateAsync({
hour: 9,
minute: 0,
weekday: 1,
repeats: true,
}).then((timestamp) => alert(new Date(timestamp!)))
}
title="Get next date for Sunday, 9 AM"
/>
);
}
_handelReceivedNotification = (notification: Notifications.Notification) => {
this.setState({
lastNotifications: notification,
});
};
_handelNotificationResponseReceived = (
notificationResponse: Notifications.NotificationResponse
) => {
console.log({ notificationResponse });
// Calling alert(message) immediately fails to show the alert on Android
// if after backgrounding the app and then clicking on a notification
// to foreground the app
setTimeout(() => Alert.alert('You clicked on the notification 🥇'), 1000);
};
private getPermissionsAsync = async () => {
const permission = await Notifications.getPermissionsAsync();
console.log('Get permission: ', permission);
alert(`Status: ${permission.status}`);
};
private requestPermissionsAsync = async () => {
const permission = await Notifications.requestPermissionsAsync();
alert(`Status: ${permission.status}`);
};
_obtainUserFacingNotifPermissionsAsync = async () => {
let permission = await Notifications.getPermissionsAsync();
if (permission.status !== 'granted') {
permission = await Notifications.requestPermissionsAsync();
if (permission.status !== 'granted') {
Alert.alert(`We don't have permission to present notifications.`);
}
}
return permission;
};
_obtainRemoteNotifPermissionsAsync = async () => {
let permission = await Notifications.getPermissionsAsync();
if (permission.status !== 'granted') {
permission = await Notifications.requestPermissionsAsync();
if (permission.status !== 'granted') {
Alert.alert(`We don't have permission to receive remote notifications.`);
}
}
return permission;
};
_presentLocalNotificationAsync = async () => {
await this._obtainUserFacingNotifPermissionsAsync();
await Notifications.scheduleNotificationAsync({
content: {
title: 'Here is a scheduled notification!',
body: 'This is the body',
data: {
hello: 'there',
future: 'self',
},
sound: true,
},
trigger: null,
});
};
_scheduleLocalNotificationAsync = async () => {
await this._obtainUserFacingNotifPermissionsAsync();
await Notifications.scheduleNotificationAsync({
content: {
title: 'Here is a local notification!',
body: 'This is the body',
data: {
hello: 'there',
future: 'self',
},
sound: true,
},
trigger: {
seconds: 10,
},
});
};
_scheduleLocalNotificationWithCustomSoundAsync = async () => {
await this._obtainUserFacingNotifPermissionsAsync();
// Prepare the notification channel
await Notifications.setNotificationChannelAsync('custom-sound', {
name: 'Notification with custom sound',
importance: Notifications.AndroidImportance.HIGH,
sound: 'cat.wav', // <- for Android 8.0+
});
await Notifications.scheduleNotificationAsync({
content: {
title: 'Here is a local notification!',
body: 'This is the body',
data: {
hello: 'there',
future: 'self',
},
sound: 'cat.wav',
},
trigger: {
channelId: 'custom-sound',
seconds: 1,
},
});
};
_scheduleLocalNotificationAndCancelAsync = async () => {
await this._obtainUserFacingNotifPermissionsAsync();
const notificationId = await Notifications.scheduleNotificationAsync({
content: {
title: 'This notification should not appear',
body: 'It should have been cancelled. :(',
sound: true,
},
trigger: {
seconds: 10,
},
});
await Notifications.cancelScheduledNotificationAsync(notificationId);
};
_incrementIconBadgeNumberAsync = async () => {
const currentNumber = await Notifications.getBadgeCountAsync();
await Notifications.setBadgeCountAsync(currentNumber + 1);
const actualNumber = await Notifications.getBadgeCountAsync();
Alert.alert(`Set the badge number to ${actualNumber}`);
};
_clearIconBadgeAsync = async () => {
await Notifications.setBadgeCountAsync(0);
Alert.alert(`Cleared the badge`);
};
_sendNotificationAsync = async () => {
const permission = await this._obtainRemoteNotifPermissionsAsync();
if (permission.status === 'granted') {
registerForPushNotificationsAsync();
}
};
_unregisterForNotificationsAsync = async () => {
try {
await Notifications.unregisterForNotificationsAsync();
} catch (e) {
Alert.alert(`An error occurred un-registering for notifications: ${e}`);
}
};
_countPresentedNotifications = async () => {
const presentedNotifications = await Notifications.getPresentedNotificationsAsync();
Alert.alert(`You currently have ${presentedNotifications.length} notifications presented`);
};
_dismissAll = async () => {
await Notifications.dismissAllNotificationsAsync();
Alert.alert(`Notifications dismissed`);
};
_dismissSingle = async () => {
const presentedNotifications = await Notifications.getPresentedNotificationsAsync();
if (!presentedNotifications.length) {
Alert.alert(`No notifications to be dismissed`);
return;
}
const identifier = presentedNotifications[0].request.identifier;
await Notifications.dismissNotificationAsync(identifier);
Alert.alert(`Notification dismissed`);
};
}
/**
* If this test is failing for you on iOS, make sure you:
*
* - Have the `remote-notification` UIBackgroundMode in app.json or info.plist
* - Included "_contentAvailable": "true" in your notification payload
* - Have "Background App Refresh" enabled in your Settings
*
* If it's still not working, try killing the rest of your active apps, since the OS
* may still decide not to launch the app for its own reasons.
*/
function BackgroundNotificationHandlingSection() {
const [showInstructions, setShowInstructions] = React.useState(false);
return (
{showInstructions ? (
setShowInstructions(false)}
title="Hide background notification handling instructions"
/>
{BACKGROUND_TEST_INFO}
) : (
{
setShowInstructions(true);
getPermissionsAndLogToken();
}}
title="Show background notification handling instructions"
/>
)}
);
}
async function getPermissionsAndLogToken() {
let permission = await Notifications.getPermissionsAsync();
if (permission.status !== 'granted') {
permission = await Notifications.requestPermissionsAsync();
if (permission.status !== 'granted') {
Alert.alert(`We don't have permission to receive remote notifications.`);
}
}
if (permission.status === 'granted') {
const { data: token } = await Notifications.getExpoPushTokenAsync();
console.log(`Got this device's push token: ${token}`);
}
}