모바일 앱에 필수적인 요소는 몇 가지가 있지만, 그중 알림 기능은 없으면 서운하다고 생각한다.
알림을 통해 사용자에게 필요한 정보를 보낼 수도 있고, 접속률도 높일 수 있으니까.
expo-notifications 라이브러리 없이도 푸시 알림을 구현하는것은 가능하지만
해당 라이브러리를 이용하면 좋은 점이 몇개 있는데,
Androids, iOS 환경에 따라 다르게 구현해야 할 알림 전송 기능을 하나로 퉁칠 수 있다.foreground(현재 사용자가 사용중인 상태), background(백그라운드 혹은 꺼져있는 상태) 일때의 처리를 쉽게 구현할 수 있다.한 가지 주의할 점은, 실제로 알림이 오는지 테스트할 땐 시뮬레이터 말고 실기기를 사용해야한다.
시뮬레이터는 토큰이 안온다.
import * as Device from "expo-device";
import * as Notifications from "expo-notifications";
import Constants from "expo-constants";
import { Platform } from "react-native";
function handleRegistrationError(errorMessage: string) {
alert(errorMessage);
throw new Error(errorMessage);
}
export async function RegisterForPushNotificationsAsync() {
if (Platform.OS === "android") {
Notifications.setNotificationChannelAsync("default", {
name: "default",
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: "#FF231F7C",
});
}
if (Device.isDevice) {
const { status: existingStatus } =
await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== "granted") {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== "granted") {
handleRegistrationError(
"Permission not granted to get push token for push notification!"
);
return;
}
const projectId =
Constants?.expoConfig?.extra?.eas?.projectId ??
Constants?.easConfig?.projectId;
if (!projectId) {
handleRegistrationError("Project ID not found");
}
try {
const pushTokenString = (
await Notifications.getExpoPushTokenAsync({
projectId,
})
).data;
console.log(pushTokenString);
return pushTokenString;
} catch (e: unknown) {
handleRegistrationError(`${e}`);
}
} else {
handleRegistrationError("Must use physical device for push notifications");
}
}
푸시 알림 허용과 함께 푸시 토큰을 받아오는 코드다. 공식 문서에서 잘 짜줬으니 그대로 사용하면 된다.
여기서 projectId는 말 그대로 내 프로젝트 id이다.
이렇게 받아온 expo push token은 https://expo.dev/notifications에서 테스트하거나, 서버에 건네서 알람을 보내게 하는 용도로 사용할 수 있다.
앱을 사용 중일땐 기본적으로 시스템 알람이 뜨지 않는다.
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldPlaySound: true,
shouldSetBadge: true,
shouldShowBanner: true,
shouldShowList: true,
shouldShowAlert: true,
}),
});
이런식으로 선언해주면 앱을 사용중일때도 시스템 알람처럼 작동한다!
또한 알람을 받았을 때 내가 원하는 형태로 보여주는 것도 가능한데,
const [notification, setNotification] = useState<
Notifications.Notification | undefined
>(undefined);
notification 변수를 선언해주고,
const notificationListener = Notifications.addNotificationReceivedListener(
(notification) => {
setNotification(notification);
}
);
Notifications.addNotificationReceivedListener 메서드를 이용해 저장한 후, 데이터를 이용해 원하는 형태로 띄우는 것도 가능할 것이다.
알람을 눌렀을 때 어떤 행동을 할지에 대해서도 정해야 한다. 가령, 데이터를 담은 모달을 띄운다던가, 앱 내의 어딘가로 이동시킨다던가(Deep Link) 등의 행동이 있을 것 같다.
import { useEffect } from 'react';
import * as Notifications from 'expo-notifications';
import { router } from 'expo-router';
function useNotificationObserver() {
useEffect(() => {
let isMounted = true;
function redirect(notification: Notifications.Notification) {
const url = notification.request.content.data?.url;
if (url) {
router.push(url);
}
}
Notifications.getLastNotificationResponseAsync()
.then(response => {
if (!isMounted || !response?.notification) {
return;
}
redirect(response?.notification);
});
const subscription = Notifications.addNotificationResponseReceivedListener(response => {
redirect(response.notification);
});
return () => {
isMounted = false;
subscription.remove();
};
}, []);
}
export default function Layout() {
useNotificationObserver();
return <Slot />;
}
expo router를 이용할 때의 예제인데, 실제 우리 앱에선 특정 링크를 알림이 담고 있다면 웹뷰에 해당 url을 부여하는 식으로 구현이 가능할 것 같다는 생각이 든다.