I'm using expo-notifications and Firebase Cloud Messaging (FCM) to handle push notifications in my React Native app. However, I'm encountering an issue where notifications only arrive when the app is in the foreground or background. They are not received when the app is completely closed.
Here's the relevant code:
App.tsx
import messaging from "@react-native-firebase/messaging";
import * as Notifications from "expo-notifications";
import { useEffect } from "react";
import { PermissionsAndroid, Platform } from "react-native";
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
export default function App() {
const requestNotificationPermission = async () => {
try {
await messaging().requestPermission();
} catch (error) {
console.error("Error requesting notifications permission:", error);
}
};
useEffect(() => {
if (Platform.OS === "android") {
PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
);
} else {
requestNotificationPermission();
}
}, []);
return (
...
<BottomTabNavigator />
...
)
}
BottomTabNavigator.tsx
import messaging from "@react-native-firebase/messaging";
import { NavigationProp, useNavigation } from "@react-navigation/native";
import * as Notifications from "expo-notifications";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import Toast from "react-native-toast-message";
import { useLoggedUser, ToastNotification, registerNotifications } from "@utils";
export function BottomTabNavigator() {
const { t } = useTranslation();
const { fetchUser, setDeviceId } = useLoggedUser();
const navigation = useNavigation<NavigationProp<RootTabParamList>>();
useEffect(() => {
fetchUser();
registerNotifications().then(
(deviceId) => deviceId && setDeviceId(deviceId)
);
// CLICK HANDLER
const handleNotificationClick = async (
response: Notifications.NotificationResponse
) => {
const screen = response?.notification?.request?.content?.data?.screen;
if (screen !== null) {
navigation.navigate(screen);
}
};
// CLICK LISTENER
const notificationClickSubscription =
Notifications.addNotificationResponseReceivedListener(
handleNotificationClick
);
// BACKGROUND HANDLER
messaging().setBackgroundMessageHandler(async (remoteMessage) => {
console.log("Message handled in the background!", remoteMessage);
const notification = {
title: remoteMessage.notification?.title,
body: remoteMessage.notification?.body,
data: remoteMessage.data, // optional data payload
};
// Schedule the notification with a null trigger to show immediately
await Notifications.scheduleNotificationAsync({
content: notification,
trigger: null,
});
});
// FOREGROUND HANDLER
const handlePushNotification = async () => {
ToastNotification({
title: t("notifications.new"),
type: "info",
onPress: () => {
navigation.navigate("Settings");
Toast.hide();
},
});
};
// FOREGROUND LISTENER
const unsubscribe = messaging().onMessage(handlePushNotification);
// Clean up the event listeners
return () => {
unsubscribe();
notificationClickSubscription.remove();
};
}, []);
}
I've noticed that if I populate the body of the push notification with notification object, I receive two notifications when the app is foreground or background: one from Expo and one from Firebase. To avoid this redundancy, I'm populating only the data object. However, this leads to notifications not being displayed when the app is closed (only the one from Firebase).
I need to use expo-notifications to handle the click on notification and group the messages.
I've tried two approaches:
Populating both body and data objects:
This resulted in receiving two notifications: one from Expo and one from Firebase. While the Firebase notification was still delivered even when the app was closed, it created redundancy in the foreground and background states.
Populating only the body object with data:
This avoided duplicate notifications, but notifications weren't displayed when the app was completely closed. This is the behavior I'm trying to overcome.
In both cases, I expected notifications to be delivered regardless of the app state (foreground, background, or closed).
Ideally, I'd like to achieve this functionality with data-only notifications, without creating duplicate notifications.