When I launch the application via Expo Go and test the notification, the target screen opens after clicking on it. However, when I build the APK and run it on a physical device, nothing happens when I click on the received notification.
When I check the log through Android Studio / DEBUG, I get the following:
2024-08-08 23:44:01.315 24487-24506 EGL_emulation my.app.package.name D app_time_stats: avg=33.51ms min=12.44ms max=72.02ms count=27
2024-08-08 23:44:01.836 24487-24491 my.app.package.name I NativeAlloc concurrent mark compact GC freed 7901KB AllocSpace bytes, 30(936KB) LOS objects, 49% free, 11MB/23MB, paused 10.083ms,2.385ms total 299.526ms
2024-08-08 23:44:03.446 24487-24506 EGL_emulation my.app.package.name D app_time_stats: avg=97.43ms min=3.60ms max=1518.18ms count=21
2024-08-08 23:44:04.481 24487-24506 EGL_emulation my.app.package.name D app_time_stats: avg=15.67ms min=2.44ms max=38.54ms count=45
2024-08-08 23:44:05.517 24487-24506 EGL_emulation my.app.package.name D app_time_stats: avg=53.96ms min=4.06ms max=572.25ms count=18
2024-08-08 23:44:05.578 24487-24649 notifications my.app.package.name E Couldn't get channel for the notifications - trigger is 'null'. Fallback to 'expo_notifications_fallback_notification_channel' channel
2024-08-08 23:44:06.933 24487-24506 EGL_emulation my.app.package.name D app_time_stats: avg=109.35ms min=2.31ms max=1171.37ms count=12
2024-08-08 23:44:08.011 24487-24506 EGL_emulation my.app.package.name D app_time_stats: avg=31.38ms min=3.04ms max=228.85ms count=29
2024-08-08 23:44:09.094 24487-24487 VRI[MainActivity] my.app.package.name D visibilityChanged oldVisibility=true newVisibility=false
2024-08-08 23:44:09.227 24487-24559 ReactNativeJNI my.app.package.name I Memory warning (pressure level: TRIM_MEMORY_UI_HIDDEN) received by JS VM, ignoring because it's non-severe
2024-08-08 23:44:11.632 24487-24487 ReactNativeJS my.app.package.name D [native] ExpoNotificationLifecycleListener contains an unmarshaled notification response. Skipping.
2024-08-08 23:44:11.650 24487-24559 ReactNativeJS my.app.package.name W Event data is missing in the notification payload.
2024-08-08 23:44:11.650 24487-24559 ReactNativeJS my.app.package.name W Event data is missing in the notification payload.
2024-08-08 23:44:13.298 24487-24506 EGL_emulation my.app.package.name D app_time_stats: avg=71.31ms min=13.70ms max=216.31ms count=17
2024-08-08 23:44:16.131 24487-24506 EGL_emulation my.app.package.name D app_time_stats: avg=89.93ms min=3.68ms max=2012.98ms count=30
2024-08-08 23:44:17.139 24487-24506 EGL_emulation my.app.package.name D app_time_stats: avg=16.40ms min=3.34ms max=37.08ms count=45
2024-08-08 23:44:19.622 24487-24506 EGL_emulation my.app.package.name D app_time_stats: avg=139.47ms min=3.89ms max=2030.02ms count=17
2024-08-08 23:44:20.639 24487-24506 EGL_emulation my.app.package.name D app_time_stats: avg=26.14ms min=6.18ms max=69.69ms count=30
What I get when I do the same process in the Expo Go environment locally:
and requesting notification permissions...
LOG Resources loaded, navigation is ready.
LOG ExponentPushToken[anamAFPyKU4GsU7YpbF20f]
LOG Scheduling notifications for event: {"field1": "0.00", "field2": "2024-08-08", "field3": "XX.XXXXXX", "field4": "some_value", "field5": "XX.XXXXX", "field6": "another_value", "field7": "yet_another_value", "field8": "123123123", "field9": "20:00:00", "field10": "12:00:00", "field11": "2", "field12": "../public/template/images/placeholder/1.jpg"}
LOG Scheduling immediate notification: {"attachments": [{"url": "https://example.com/admin/public/template/images/placeholder/1.jpg"}], "body": "Notification message.", "data": {"event": {"field1": "0.00", "field2": "2024-08-08", "field3": "XX.XXXXX", "field4": "some_value", "field5": "XX.XXXXX", "field6": "another_value", "field7": "yet_another_value", "field8": "123123123", "field9": "20:00:00", "field10": "12:00:00", "active": undefined, "field11": "2", "id": "2", "image": "../public/template/images/placeholder/1", "imagePath": "../public/template/images/placeholder/1", "field12": XX.XXXXX, "field13": "some_value", "field14": XX.XXXXX, "field15": "another_value", "field16": "123123123", "field17": "0.00", "field18": "12:00:00 - 20:00:00"}}, "sound": "default", "title": "Notification title header"}
LOG Notification response: {
"notification": {
"request": {
"trigger": null,
"content": {
"title": "Header title",
"badge": null,
"autoDismiss": true,
"data": {
"event": {
"field1": "XX.XXXXX",
"field2": "2",
"field3": "some_value",
"field4": "0.00",
"field5": "0.00",
"field6": "123123123",
"field7": "../public/template/images/placeholder/1.jpg",
"field8": "2024-08-08",
"field9": "another_value",
"field10": "2024-08-08",
"field11": "12:00:00 - 20:00:00",
"field12": XX.XXXXX,
"field13": "yet_another_value",
"field14": "another_value",
"id": "2",
"field15": "20:00:00",
"field16": "123123123",
"field17": "some_value",
"field18": "XX.XXXXX",
"field19": "12:00:00",
"field20": "../public/template/images/placeholder/1.jpg",
"field21": XX.XXXXX,
"field22": "another_value"
}
},
"body": "Notification message",
"sound": "custom",
"sticky": false,
"subtitle": null
},
"identifier": "285406aa-4d79-4889-85c7-8f162eed835e"
},
"date": 1723155191850
},
"actionIdentifier": "expo.modules.notifications.actions.DEFAULT"
}
LOG Notification response: {
"notification": {
"request": {
"trigger": null,
"content": {
"title": "example",
"badge": null,
"autoDismiss": true,
"data": {
"event": {
"field1": "XX.XXXXX",
"field2": "2",
"field3": "some_value",
"field4": "0.00",
"field5": "0.00",
"field6": "123123123",
"field7": "../public/template/images/placeholder/1.jpg",
"field8": "2024-08-08",
"field9": "another_value",
"field10": "2024-08-08",
"field11": "12:01:00 - 20:00:00",
"field12": XX.XXXXX,
"field13": "yet_another_value",
"field14": "another_value",
"id": "2",
"field15": "20:00:00",
"field16": "123123123",
"field17": "some_value",
"field18": "XX.XXXXX",
"field19": "12:01:00",
"field20": "../public/template/images/placeholder/1.jpg",
"field21": XX.XXXXX,
"field22": "another_value"
}
},
"body": "Notification message",
"sound": "custom",
"sticky": false,
"subtitle": null
},
"identifier": "285406aa-4d79-4889-85c7-8f162eed835e"
},
"date": 1723155191850
},
"actionIdentifier": "expo.modules.notifications.actions.DEFAULT"
}
LOG Notification response received: {"actionIdentifier": "expo.modules.notifications.actions.DEFAULT", "notification": {"date": 1723155191850, "request": {"content": [Object], "identifier": "285406aa-4d79-4889-85c7-8f162eed835e", "trigger": null}}}
And when I launch the application through Expo Go and click on the notification, it opens the screen I need, while nothing opens in production.
App.js:
import React, { useEffect, useState, useRef, useCallback } from 'react';
import * as SplashScreen from 'expo-splash-screen';
import * as Font from 'expo-font';
import Navigation from './Components/StackNavigation';
import * as Notifications from 'expo-notifications';
import { useNavigationContainerRef } from '@react-navigation/native';
import * as Linking from 'expo-linking';
import { Platform, Alert } from 'react-native';
if (process.env.NODE_ENV !== 'development') {
console.log = () => {};
}
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
SplashScreen.preventAutoHideAsync();
const registerForPushNotificationsAsync = async () => {
let token;
if (Platform.OS === 'android') {
await Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
}
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
Alert.alert("Permissions required", "You need to enable notifications permissions.");
return;
}
token = (await Notifications.getExpoPushTokenAsync()).data;
console.log(token);
return token;
};
export default function App() {
const [fontsLoaded, setFontsLoaded] = useState(false);
const navigationRef = useNavigationContainerRef();
const isNavigationReady = useRef(false);
const pendingNotificationResponse = useRef(null);
const prefix = Linking.createURL('/');
const handleNotificationResponse = useCallback((response) => {
console.log('Notification response:', JSON.stringify(response, null, 2));
const event = response.notification.request.content.data?.event;
if (event) {
if (isNavigationReady.current) {
navigationRef.current?.navigate('Event Details', { event });
} else {
pendingNotificationResponse.current = response;
}
} else {
console.warn('Event data is missing in the notification payload.');
}
}, []);
useEffect(() => {
registerForPushNotificationsAsync();
const subscription = Notifications.addNotificationResponseReceivedListener(handleNotificationResponse);
return () => subscription.remove();
}, [handleNotificationResponse]);
useEffect(() => {
async function loadResourcesAndDataAsync() {
try {
console.log('Loading fonts and requesting notification permissions...');
await Font.loadAsync({
// Load fonts
});
const { status } = await Notifications.requestPermissionsAsync();
if (status !== 'granted') {
alert('Sorry, we need notification permissions to make this work!');
}
} catch (e) {
console.warn(e);
} finally {
console.log('Resources loaded, navigation is ready.');
setFontsLoaded(true);
SplashScreen.hideAsync();
isNavigationReady.current = true;
if (pendingNotificationResponse.current) {
handleNotificationResponse(pendingNotificationResponse.current);
pendingNotificationResponse.current = null;
}
}
}
loadResourcesAndDataAsync();
}, [handleNotificationResponse]);
if (!fontsLoaded) {
return null;
}
return (
<Navigation
ref={navigationRef}
linking={{
prefixes: [prefix],
config: {
screens: {
EventDetails: 'Event Details',
},
},
}}
/>
);
}
A screen that contains the context of notifications, etc…
import * as Notifications from 'expo-notifications';
// Set up the notification handler
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
// Function to request notification permissions
const requestNotificationPermissions = async () => {
const { status } = await Notifications.requestPermissionsAsync();
if (status !== 'granted') {
Alert.alert("Permissions required", "You need to enable notifications permissions.");
}
};
// Function to clean image URLs
const cleanUrl = (url) => {
const urlParts = url.split('/public/template/images/').pop();
return `${config.BASE_URL}project-login/public/template/images/${urlParts}`;
};
// Function to schedule notifications for events
const scheduleNotifications = async (events) => {
const now = moment();
for (const event of events) {
const eventStartDate = moment(event.date).set({
hour: parseInt(event.startTime.split(':')[0]),
minute: parseInt(event.startTime.split(':')[1]),
});
const oneDayBeforeEvent = eventStartDate.clone().subtract(1, 'days');
const oneHourBeforeEvent = eventStartDate.clone().subtract(1, 'hours');
console.log('Scheduling notifications for event:', event);
if (oneDayBeforeEvent.isAfter(now)) {
const notificationContent24h = {
title: event.name,
body: `Reminder: "${event.name}" starts in 24 hours at ${moment(event.startTime, 'HH:mm:ss').format('HH:mm')}!`,
data: { event },
sound: 'default',
attachments: [{
url: cleanUrl(event.imagePath)
}],
};
console.log('Notification content 24h:', notificationContent24h);
await Notifications.scheduleNotificationAsync({
content: notificationContent24h,
trigger: {
date: oneDayBeforeEvent.toDate(),
},
});
}
if (oneHourBeforeEvent.isAfter(now)) {
const notificationContent1h = {
title: event.name,
body: `Reminder: "${event.name}" starts in 1 hour at ${moment(event.startTime, 'HH:mm:ss').format('HH:mm')}!`,
data: { event },
sound: 'default',
attachments: [{
url: cleanUrl(event.imagePath)
}],
};
console.log('Notification content 1h:', notificationContent1h);
await Notifications.scheduleNotificationAsync({
content: notificationContent1h,
trigger: {
date: oneHourBeforeEvent.toDate(),
},
});
}
}
};
// Effect to handle incoming notification responses
useEffect(() => {
const subscription = Notifications.addNotificationResponseReceivedListener(response => {
console.log('Notification response received:', response);
if (response.notification.request.content.data && response.notification.request.content.data.event) {
const { event } = response.notification.request.content.data;
navigation.navigate('Event Details', { event });
} else {
console.warn('Event data is missing in the notification payload.');
}
});
return () => subscription.remove();
}, [navigation]);
// Function to trigger an immediate notification
const handleEventNotification = async () => {
if (savedEvents.length > 0) {
const event = savedEvents[Math.floor(Math.random() * savedEvents.length)];
const eventDate = moment(event.date).format('DD. MMMM YYYY');
const eventTime = moment(event.startTime, 'HH:mm:ss').format('HH:mm');
const notificationContent = {
title: "Event Reminder",
body: `Reminder: "${event.name}" starts at ${eventTime} on ${eventDate}.`,
data: {
event: {
...event,
id: event.event_id,
name: event.name,
description: event.description,
date: event.date,
active: event.status,
time: `${event.startTime} - ${event.endTime}`,
location: event.location,
latitude: parseFloat(event.latitude),
longitude: parseFloat(event.longitude),
phone: event.phone,
image: event.imagePath,
price: event.ticketPrice
}
},
sound: 'default',
attachments: [{
url: cleanUrl(`${config.BASE_URL}project-login/public/template/images/events/${event.imagePath}`)
}],
};
console.log('Scheduling immediate notification:', notificationContent);
await Notifications.scheduleNotificationAsync({
content: notificationContent,
trigger: null,
});
} else {
Alert.alert("No events", "You have no saved events.");
}
};
// Function to trigger a notification one hour before the event
const handleEventNotificationOneHour = async () => {
if (savedEvents.length > 0) {
const event = savedEvents[Math.floor(Math.random() * savedEvents.length)];
const eventDate = moment(event.date).format('DD. MMMM YYYY');
const eventTime = moment(event.startTime, 'HH:mm:ss').format('HH:mm');
const notificationContent = {
title: "Event Reminder",
body: `Reminder: "${event.name}" starts in 1 hour at ${eventTime} on ${eventDate}.`,
data: {
event: {
...event,
id: event.event_id,
name: event.name,
description: event.description,
date: event.date,
active: event.status,
time: `${event.startTime} - ${event.endTime}`,
location: event.location,
latitude: parseFloat(event.latitude),
longitude: parseFloat(event.longitude),
phone: event.phone,
image: event.imagePath,
price: event.ticketPrice
}
},
sound: 'default',
attachments: [{
url: cleanUrl(`${config.BASE_URL}project-login/public/template/images/events/${event.imagePath}`)
}],
};
console.log('Scheduling immediate notification (one hour before):', notificationContent);
await Notifications.scheduleNotificationAsync({
content: notificationContent,
trigger: null,
});
} else {
Alert.alert("No events", "You have no saved events.");
}
};
John is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.