iOS 푸시 알림 처리 시 예기치 않은 문제 해결기록

cansweep·2025년 2월 28일
0

문제 상황

채팅 푸시 알림이 온 상태에서 앱을 실행하면 사용자가 알림을 누르지 않았음에도 메세지의 채팅방으로 이동되는 현상이 발생했습니다. 코드를 살펴본 결과 메세지가 왔을 때 채팅방으로 이동시키고 있어 사용자의 푸시 알림 클릭 여부를 확인해 알림을 눌러 앱을 실행했을 때에만 채팅방으로 이동시키도록 수정하면 될 것으로 예상했지만 확인 결과 두 가지 문제가 있었습니다.

  • 앱 부팅 시 사용자가 알림을 클릭 했을 때만 반환되어야 하는 메세지 정보가 푸시 알림을 클릭하지 않았음에도 반환되는 현상
  • PushNotificationIOS.addEventListener를 통해 수신되는 notification이 null로 반환되는 현상

원인 분석 및 해결 과정

푸시 알림을 눌러 앱에 진입한 경우, 사용자를 바로 채팅방으로 이동시키거나 읽지 않은 알림 카운트를 초기화하는 등 적절한 액션을 수행할 수 있습니다. 이를 위해 푸시 알림이 전달된 상황을 확인하는 로직이 필요합니다.

이때 앱 상태에 따라 푸시 알림 확인 방법이 달라집니다.

  • Background, Foreground 상태: PushNotificationIOS의 notification 또는 localNotification 이벤트 핸들러를 사용해 전달된 알림을 확인합니다.
  • Quit 상태에서 Foreground 상태로 전환될 때: PushNotificationIOS.getInitialNotification() 메서드의 반환값을 통해 전달된 알림을 확인합니다.

현재, Background에서 Foreground로 전환되었을 때 푸시 알림을 처리하는 로직은 App.tsx에, Quit 상태에서 Foreground로 전환되었을 때의 로직은 앱 부팅 관련 로직 쪽에서 관리하고 있었습니다.

// App.tsx
// Background -> Foreground일 때 처리
useEffect(() => {
	PushNotificationIOS.addEventListener('notification', async (notification) => {
	  const notificationData = notification?.getData();
	  if (!notificationData) return;

    if (notificationData?.sendbird) {
      ... // 채팅방 이동 로직
    }

    notification.finish(PushNotificationIOS.FetchResult.NoData);
  });
    
  return () => {
    PushNotificationIOS.removeEventListener('notification');
  };
}, []);

// Quit -> Foreground일 때
// App Boot 관련 로직
const notificationData = await PushNotificationIOS.getInitialNotification();
if(notificationData) {
	// 채팅방 이동 로직
}

PushNotificationIOS의 공식문서에서는 사용자가 푸시 알림을 눌러서 들어왔는지 감지할 수 있는 방법을 안내하고 있습니다.

  • 이벤트 핸들러를 사용하는 경우: notification.getData().userInteraction 값이 1이면 사용자가 푸시 알림을 눌러 앱을 실행했음을 의미합니다.
  • getInitialNotification() 함수를 사용할 경우: 푸시 알림을 통해 앱이 실행되었을 때만 PushNotificationIOS 타입의 객체를 반환하며, 그 외의 경우에는 null을 반환합니다. 즉, 반환값이 null이 아니라면 사용자가 푸시 알림을 눌러 앱을 실행한 것입니다.

하지만 시뮬레이터에서 실행한 결과,

  • 이벤트 핸들러에서 notification 이벤트는 발생하지만, notification.getData()의 반환값이 null이어서 userInteraction 속성을 확인할 수 없었습니다.
  • getInitialNotification() 함수는 null을 반환해야 하는 상황에서도 null이 아닌 값을 반환하고 있었습니다.

원인을 분석하던 중, 사일런트 푸시(Silent Push) 관련 설정이 영향을 미치고 있다는 가설을 세웠습니다.

사일런트 푸시란?
사일런트 푸시는 사용자 인터페이스에 알림을 표시하지 않고 백그라운드에서 데이터를 갱신하는 푸시 알림입니다. 즉, 앱이 백그라운드 또는 종료된 상태에서도 백그라운드에서 데이터를 미리 가져올 수 있도록 도와줍니다.

현재 채팅 관련 푸시 알림은 사일런트 푸시를 사용하고 있었기에 문제에 대한 시나리오를 다음과 같이 추측했습니다.

  1. Quit/Background 상태인 앱에 사일런트 푸시 수신
  2. 사일런트 푸시로 인해 앱이 백그라운드 상태로 전환됨.
  3. 사용자가 앱 아이콘을 눌러 앱을 실행함.
  4. 부팅 시 PushNotificationIOS.getInitialNotification()이 백그라운드에서 처리된 메세지를 반환 (원래는 null을 반환해야 하는 상황)

사일런트 푸시 관련 설정을 비활성화한 후 테스트하니 위 사진과 같은 결과를 얻을 수 있었습니다.

백그라운드에서 메세지를 처리하는 과정이 빠지자 Quit 상태일 때 푸시 알림을 누르지 않고 앱에 진입하면 정상적으로 PushNotificationIOS.getInitialNotification()이 null을 반환해 채팅방으로 이동하지 않고 앱이 켜지게 되었습니다.

다만 아직도 앱이 백그라운드 상태에서 푸시알림이 온 경우에는 푸시 알림을 눌러 앱을 진입했을 때 채팅방으로 가지 않고 앱이 단순히 켜지기만 하는 문제가 남아있었습니다.

// App.tsx
// Background -> Foreground일 때 처리
useEffect(() => {
	PushNotificationIOS.addEventListener(**'notification'**, async (notification) => {
	  const notificationData = notification?.getData();
	  if (!notificationData) return;
	  
	  const isClicked = notificationData.userInteraction === 1

    if (notificationData?.sendbird && isClicked) {
      ... // 채팅방 이동 로직
    }

    notification.finish(PushNotificationIOS.FetchResult.NoData);
  });
    
  return () => {
    PushNotificationIOS.removeEventListener('notification');
  };
}, []);

기존 코드를 다시 살펴보니 addEventListener에 notification 이벤트가 연결되어 있었습니다.

Sendbird의 공식 문서에서 메세지를 수신했을 때 FCM의 BackgroundMessageHandler에서 Notifee를 이용해 수신한 메세지 내용을 로컬 알림으로 보여주고 있었고 푸시 알림 클릭을 다루기 위해 localNotification 이벤트에 이벤트 핸들러를 연결한다는 내용을 확인할 수 있었습니다.

따라서 기존 notification 이벤트를 localNotification 이벤트로 수정하니 백그라운드 상태에서도 채팅 푸시 알림을 눌렀을 때 정상적으로 채팅방으로 이동되는 것을 확인할 수 있었습니다.

profile
하고 싶은 건 다 해보자! 를 달고 사는 프론트엔드 개발자입니다.

0개의 댓글