When I test the notification system in a test environment using expo go, everything works as it should. However, when I create an APK through expo and install it on the phone, everything works except when I click on the notification, there is a problem because it does not redirect to the finished screen.
Screen1.js:
const getStoredNotifications = async () => {
const notifications = await AsyncStorage.getItem('notifications');
return notifications ? JSON.parse(notifications) : [];
};
const storeNotification = async (notificationId) => {
const notifications = await getStoredNotifications();
const updatedNotifications = [...notifications, notificationId];
await AsyncStorage.setItem('notifications', JSON.stringify(updatedNotifications));
};
const scheduleReminder = async (item, immediate = false) => {
const notifications = await getStoredNotifications();
if (!immediate && notifications.includes(item.id)) {
console.log(`Reminder already scheduled for item: ${decode(item.name)}`);
return;
}
const itemStartDateTime = new Date(`${item.date}T${item.startTime}`);
const triggerDate = immediate ? new Date(Date.now() + 5 * 1000) : new Date(itemStartDateTime.getTime() - 60 * 60 * 1000);
if (triggerDate > new Date()) {
const latitude = parseFloat(item.latitude);
const longitude = parseFloat(item.longitude);
console.log(`Parsed coordinates: Latitude = ${latitude}, Longitude = ${longitude}`);
if (!isNaN(latitude) && !isNaN(longitude)) {
const formattedItem = {
id: item.id,
name: item.name,
description: item.description,
date: item.date,
time: `${item.startTime} - ${item.endTime}`,
location: item.location,
latitude: latitude,
longitude: longitude,
phone: item.phone,
image: item.imagePath,
price: item.ticketPrice,
};
console.log('Scheduling reminder with item:', JSON.stringify(formattedItem));
const notificationId = await Notifications.scheduleNotificationAsync({
content: {
title: `Starting soon!`,
body: `Your item ${formattedItem.name} starts ${immediate ? 'soon' : 'in 1 hour'}.`,
data: { item: formattedItem, channelId: 'reminders' },
},
trigger: { date: triggerDate },
channelId: 'reminders',
});
console.log(`Reminder scheduled for item: ${formattedItem.name} with ID: ${notificationId}`);
if (!immediate) {
await storeNotification(item.id);
}
} else {
console.warn('Invalid coordinates received in reminder:', `Latitude: ${latitude}`, `Longitude: ${longitude}`);
}
} else {
console.log(`Trigger date is in the past. No reminder scheduled for item: ${item.name}`);
}
};
const fetchStoredItems = useCallback(async () => {
setLoading(true);
try {
const user = JSON.parse(await AsyncStorage.getItem('user'));
if (!user) {
Alert.alert("Not Logged In", "Please log in.");
return;
}
const response = await fetch(`${config.BASE_URL}api/fetchStoredItems?userId=${user.id}`);
const data = await response.json();
if (data.success) {
const validItems = data.items.filter(item => {
const itemEndDate = moment(item.date).add(7, 'days');
const isArchived = item.status === 'Archived';
const isPastEndDate = moment().isAfter(itemEndDate);
return !isArchived && !isPastEndDate;
});
setStoredItems(validItems);
const notifications = await getStoredNotifications();
const unprocessedItems = validItems.filter(item => !notifications.includes(item.id));
for (const item of unprocessedItems) {
await scheduleReminder(item);
}
} else {
Alert.alert("Error", "Unable to retrieve stored items.");
}
} catch (error) {
console.error('Failed to fetch stored items', error);
Alert.alert("Error", "Network issue.");
} finally {
setLoading(false);
}
}, []);
const handleTestReminder = async () => {
if (storedItems.length === 0) {
Alert.alert("No Stored Items", "You currently have no stored items to test.");
return;
}
const item = storedItems[0];
await scheduleReminder(item, true);
};
<TouchableOpacity onPress={handleTestReminder} style={styles.testButton}>
<Text style={styles.testButtonText}>Test Reminder</Text>
</TouchableOpacity>
App.js:
import React, { useEffect, useRef, useState } 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 { navigationRef } from './Components/NavigationService';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { decode } from 'utf8';
SplashScreen.preventAutoHideAsync();
export default function App() {
const [fontsLoaded, setFontsLoaded] = useState(false);
const [isNavigating, setIsNavigating] = useState(false);
const notificationListener = useRef(null);
const checkNotificationPermissions = async () => {
const { status } = await Notifications.getPermissionsAsync();
let finalStatus = status;
if (status !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
Alert.alert('Permissions', 'Notification permission is not enabled.');
return false;
}
return true;
};
const saveReminderData = async (item) => {
try {
const itemData = JSON.stringify(item);
await AsyncStorage.setItem(`reminder_${item.id}`, itemData);
console.log('Reminder data saved locally.');
} catch (error) {
console.error('Failed to save reminder data:', error);
}
};
const removeReminderData = async (itemId) => {
try {
await AsyncStorage.removeItem(`reminder_${itemId}`);
console.log('Reminder data removed.');
} catch (error) {
console.error('Failed to remove reminder data:', error);
}
};
useEffect(() => {
async function loadResourcesAndDataAsync() {
try {
const hasPermission = await checkNotificationPermissions();
if (!hasPermission) {
console.log('Notifications are not allowed');
}
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
});
const existingChannel = await Notifications.getNotificationChannelAsync('reminders');
if (!existingChannel) {
await Notifications.setNotificationChannelAsync('reminders', {
name: 'Reminders',
importance: Notifications.AndroidImportance.HIGH,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
console.log('Notification channel created: reminders');
} else {
console.log('Notification channel already exists:', existingChannel);
}
await Font.loadAsync({
// Fonts
});
const handleNotificationResponse = async (response) => {
console.log('Notification response received:', response);
const { identifier, content } = response.notification?.request || {};
const channelId = content?.data?.channelId || 'reminders';
console.log(`Notification ID: ${identifier}`);
console.log(`Notification Channel: ${channelId}`);
const item = content?.data?.item;
console.log('Received item data:', item);
if (item) {
const formattedItem = {
id: item.id,
name: item.name,
description: item.description || 'No description',
date: item.date,
time: item.time,
location: item.location || 'N/A',
latitude: parseFloat(item.latitude),
longitude: parseFloat(item.longitude),
phone: item.phone || 'No phone',
image: item.image,
price: item.price || 'N/A',
openedFromNotification: true,
};
await removeReminderData(item.id);
if (!isNaN(formattedItem.latitude) && !isNaN(formattedItem.longitude)) {
setIsNavigating(true);
navigationRef.current?.navigate('Item Details', { item: formattedItem });
setTimeout(() => setIsNavigating(false), 1000);
} else {
console.warn('Invalid coordinates received in notification');
}
} else {
console.error('Notification data is missing or invalid', item);
}
};
const subscription = Notifications.addNotificationResponseReceivedListener(handleNotificationResponse);
return () => {
subscription.remove();
};
} catch (e) {
console.warn(e);
} finally {
setFontsLoaded(true);
SplashScreen.hideAsync();
}
}
loadResourcesAndDataAsync();
}, []);
if (!fontsLoaded) {
return null;
}
return <Navigation />;
}
When I run in production, and look at the logs, this shows me:
2024-08-11 23:31:35.473 19652-19690 EGL_emulation com.example.MyApplication D app_time_stats: avg=34.05ms min=10.96ms max=140.43ms count=28
2024-08-11 23:31:37.991 19652-19690 EGL_emulation com.example.MyApplication D app_time_stats: avg=176.36ms min=14.18ms max=2028.39ms count=14
2024-08-11 23:31:39.013 19652-19690 EGL_emulation com.example.MyApplication D app_time_stats: avg=30.25ms min=2.52ms max=98.53ms count=30
2024-08-11 23:31:39.202 19652-19707 ReactNativeJS com.example.MyApplication I Parsed coordinates: Latitude = 44.77935, Longitude = 20.439291
2024-08-11 23:31:39.523 19652-19707 ReactNativeJS com.example.MyApplication I Notification scheduled for event: Children's Summer Games with ID: bd4741d9-416f-4e3b-87b6-7aac3fa6e574
2024-08-11 23:31:41.458 19652-19690 EGL_emulation com.example.MyApplication D app_time_stats: avg=123.84ms min=2.02ms max=1899.74ms count=19
2024-08-11 23:31:42.483 19652-19690 EGL_emulation com.example.MyApplication D app_time_stats: avg=20.06ms min=2.49ms max=33.73ms count=39
2024-08-11 23:31:44.474 19652-19807 expo-notifications com.example.MyApplication D Notification request "bd4741d9-416f-4e3b-87b6-7aac3fa6e574" will not trigger in the future, removing.
2024-08-11 23:31:44.946 19652-19690 EGL_emulation com.example.MyApplication D app_time_stats: avg=130.94ms min=2.37ms max=2005.04ms count=18
2024-08-11 23:31:45.967 19652-19690 EGL_emulation com.example.MyApplication D app_time_stats: avg=26.19ms min=4.42ms max=58.36ms count=33
2024-08-11 23:31:48.521 19652-19652 ReactNativeJS com.example.MyApplication D [native] ExpoNotificationLifecycleListener contains an unmarshaled notification response. Skipping.
2024-08-11 23:31:48.731 19652-19707 ReactNativeJS com.example.MyApplication I 'Notification response received:', { notification:
{ request:
{ trigger:
{ channelId: null,
value: 1723411904202,
repeats: false,
type: 'date' },
content:
{ autoDismiss: true,
title: 'Starting Soon!',
badge: null,
sticky: false,
sound: 'default',
body: 'Your event Children's Summer Games is starting soon.',
subtitle: null },
identifier: 'bd4741d9-416f-4e3b-87b6-7aac3fa6e574' },
date: 1723411904459 },
actionIdentifier: 'expo.modules.notifications.actions.DEFAULT' }
2024-08-11 23:31:48.731 19652-19707 ReactNativeJS com.example.MyApplication I Notification ID: bd4741d9-416f-4e3b-87b6-7aac3fa6e574
2024-08-11 23:31:48.731 19652-19707 ReactNativeJS com.example.MyApplication I Notification Channel: reminders
2024-08-11 23:31:48.732 19652-19707 ReactNativeJS com.example.MyApplication E 'Notification data is missing or invalid', undefined
2024-08-11 23:31:48.883 19652-19708 unknown:ReactNative com.example.MyApplication E console.error: Notification data is missing or invalid undefined, js engine: hermes, stack:
_construct@1:141787
Wrapper@1:141441
_callSuper@1:139426
SyntheticError@1:140918
reactConsoleErrorHandler@1:140579
?anon_0_@1:733473
asyncGeneratorStep@1:120466
_next@1:120723
anonymous@1:120675
anonymous@1:120596
handleNotificationResponse@1:733873
anonymous@1:1623892
2024-08-11 23:31:49.140 19652-19690 EGL_emulation com.example.MyApplication D app_time_stats: avg=206.85ms min=15.57ms max=2618.55ms count=15
2024-08-11 23:31:50.145 19652-19690 EGL_emulation com.example.MyApplication D app_time_stats: avg=31.36ms min=2.32ms max=67.14ms count=25
2024-08-11 23:31:50.703 19652-19657 MyApplication com.example.MyApplication W Cleared Reference was only reachable from finalizer (only reported once)
2024-08-11 23:31:50.747 19652-19657 MyApplication com.example.MyApplication I Background concurrent mark compact GC freed 8462KB AllocSpace bytes, 22(1008KB) LOS objects, 49% free, 11MB/23MB, paused 1.575ms,2.825ms total 180.853ms
2024-08-11 23:31:52.680 19652-19690 EGL_emulation com.example.MyApplication D app_time_stats: avg=137.83ms min=13.51ms max=2026.10ms count=18
2024-08-11 23:31:53.683 19652-19690 EGL_emulation com.example.MyApplication D app_time_stats: avg=20.53ms min=2.39ms max=39.92ms count=37