FCM & 딥링크

함민혁·2025년 11월 12일

FCM(Firebase Cloud Messaging)

구글에서 제공하는 푸시 알림 인프라

  • 서버 → Firebase → 각 기기(안드로이드/iOS)로 메시지 전달.
  • Expo에서는 expo-notifications 라이브러리를 통해 FCM을 사용.

푸시 알림 클릭 시 앱의 특정 화면으로 이동하게 해주는 데이터.

알림에는 단순히 텍스트말고도 data에 { type, target } 같이 정보를 담아 보냄.

  • 예: { type: 'ORDER', target: '12345' } → 해당 주문 상세 페이지 열기

Firebase 콘솔

Firebase 프로젝트 생성

안드로이드 앱 등록 google-services.json 다운로드

iOS 앱 등록 GoogleService-Info.plist 다운로드

Expo 설정

expo-notifications, expo-device, expo-constants 설치

app.json에 FCM 관련 설정 추가 (Expo가 빌드 시 포함시켜줌)

앱실행 ~ 푸쉬알림 전송 과정

앱에서 FCM 토큰을 발급(registerForPushNotificationsAsync), 기기정보 수집(getDeviceInfo)

↓

사용자 로그인 시 백엔드에게 전달(sendNotificationToken)

↓

백엔드에서는 이벤트 발생 시 특정 혹은 전체 사용자에 대해 FCM 서버에 요청보냄

↓

FCM 서버는 안드로이드 기기에는 직접 전달 iOS 기기에는 APNs를 거쳐서 전달

앱에서 FCM 토큰을 발급(registerForPushNotificationsAsync)

FCM 토큰을 expo-notifications로 발급받음

이때 EAS 프로젝트 ID가 필요하기 때문에 expo-constants로 설정값을 읽어서 같이 넘겨줌

이 토큰을 알아야 서버가 특정 사용자 기기만 골라서 알림을 보낼 수 있음

export async function registerForPushNotificationsAsync() {
  let token;

  if (Platform.OS === 'android') {
    await 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') {
      console.log('Failed to get push token for push notification!');
      return;
    }

    token = await Notifications.getExpoPushTokenAsync({
      projectId: Constants.expoConfig?.extra?.eas?.projectId,
    });
  } else {
    console.log('Must use physical device for Push Notifications');
  }

  return token;
}

기기정보 수집(getDeviceInfo)

expo-device 로 디바이스 정보 가져옴

// 기기 정보 가져오기
export async function getDeviceInfo() {
  const deviceType = await Device.getDeviceTypeAsync();
  const deviceName = Device.deviceName;
  const osName = Device.osName;
  const osVersion = Device.osVersion;
  const brand = Device.brand;
  const manufacturer = Device.manufacturer;
  const modelName = Device.modelName;

  // 고유 식별자 생성
  const uniqueId = `${brand}-${modelName}-${osVersion}-${deviceName}`
    .replace(/\s+/g, '-')
    .toLowerCase();

  return {
    installationId: uniqueId,
    deviceName,
    deviceType,
    osName,
    osVersion,
    brand,
    manufacturer,
    modelName,
  };
}

사용자 로그인 시 백엔드에게 전달(sendNotificationToken)

이제 로그인 할때 fcmToken이랑 deviceInfo를 백엔드에게 보내줌

export const sendNotificationToken = async (body: {
  fcmToken: string;
  deviceInfo: string;
}) => {
  return await baseInstance.post('/api/notification/token', body);
};

그럼 백엔드에서 FCM API를 호출해서 앱기기로 알림을 전달함

푸쉬 알림 수신 과정

알림을 받을 때 앱이 가지는 상태는 총 세가지임

  • 앱이 켜져있을 때
  • 앱이 백그라운드에 있을 때
  • 앱이 완전히 꺼져있을 때

알림이 도착했을 때 실행할 함수(onNotificationReceived),
알림을 클릭했을 때 실행할 함수(onNotificationResponseReceived)

export function setupNotificationListeners(
  onNotificationReceived: (notification: Notifications.Notification) => void,
  onNotificationResponseReceived: (
    response: Notifications.NotificationResponse,
  ) => void,
) {

  // 안드로이드 cold-start
	Notifications.getLastNotificationResponseAsync().then((lastResponse) => {
	  if (lastResponse) {
	    onNotificationResponseReceived(lastResponse);
	  }
	});
	
  const notificationListener = Notifications.addNotificationReceivedListener(
    onNotificationReceived,
  );
  
  const responseListener =
    Notifications.addNotificationResponseReceivedListener(
      onNotificationResponseReceived,
    );

  return () => {
    Notifications.removeNotificationSubscription(notificationListener);
    Notifications.removeNotificationSubscription(responseListener);
  };
}
// app/_layout.tsx
const unsubscribe = setupNotificationListeners(
  (notification: any) => {
    console.log('알림 수신:', notification.request.content.data);
  },
  (response: any) => {
    console.log('알림 클릭:', response.notification.request.content.data);

    const deepLinkData = extractDeepLinkData(response);

    if (deepLinkData) {
      const currentTabDeepLinkHandler = (global as any)[
        `handleDeepLinkFromNotification_${currentTab}`
      ];
      if (currentTabDeepLinkHandler) {
        currentTabDeepLinkHandler(deepLinkData);
      } else {
        console.log(
          `${currentTab} 탭의 딥링크 처리 함수가 등록되지 않았습니다.`,
        );
      }
    } else {
      console.log('딥링크 데이터가 없습니다.');
    }
  },
);

앱이 켜져있을 때(Foreground)

onNotificationReceived 실행됨

사용자가 알림 클릭 시 onNotificationResponseReceived 실행됨

앱이 백그라운드에 있을 때

알림은 OS가 표시

onNotificationReceived 실행X

사용자가 알림 클릭 시 onNotificationResponseReceived 실행됨

앱이 완전히 꺼져있을 때

iOS

알림은 OS가 표시

onNotificationReceived 실행X

사용자가 알림 클릭 시 onNotificationResponseReceived 실행됨

Android
알림은 OS가 표시

onNotificationReceived 실행X

사용자가 알림 클릭 → 앱 실행되지만, listener(onNotificationResponseReceived)는 자동 실행되지 않음

아래 이 코드를 호출해서 마지막 알림 응답을 수동으로 꺼내와야 함

Notifications.getLastNotificationResponseAsync().then((lastResponse) => {
  if (lastResponse) {
    onNotificationResponseReceived(lastResponse);
  }
});

알림이 도착했을때 실시간 UI반영이 필요없다면 onNotificationReceived 는 없어도됨.(마켓D도 필요없음)

딥링크

푸쉬알림을 클릭했을 때

백엔드에서 보내준 데이터를 받아 현재 탭의 웹뷰에 postMessage로 전달해서 해당 페이지를 열도록 해야됨

푸쉬알림 클릭

↓

extractDeepLinkData 로 데이터 뽑아냄

↓

현재 탭에 맞는 딥링크처리함수를 전역객체(global)에서 꺼내옴 ex)handleDeepLinkFromNotification_Home

↓

해당 함수 실행 → useDeepLink 내부의 handleDeepLinkFromNotification 동작

↓

이미 처리된 알림(lastHandledId)인지 검사. (홈탭일때 이미 처리한 알림인데 다른 탭에서

새로운 알림이면 메세지 객체 생성

↓

WebView 준비 여부 확인

준비됨(앱이 백그라운드에 있거나, 켜져있을 경우) → webviewRef.postMessage() 로 곧바로 전달

준비 안 됨(앱이 완전히 꺼져있을 경우) → pendingDeepLink에 임시 저장

↓

WebView가 준비 완료 신호(setIsWebviewReady(true)) 보내면 pendingDeepLink 실행

앱이 켜져있거나, 백그라운드에 있을때

앱이 켜져있으니, 웹뷰도 이미 READY 상태 → 푸쉬알림 클릭 →handleDeepLinkFromNotification_home 호출 → isWebviewReadyRef.current === true 라서 바로 웹뷰로 postMessage 전송

앱이 꺼져있을때

푸쉬알림 클릭 → 앱실행 →앱코드에서 useDeepLink 실행 → 아직 READY 상태 아님 → pendingDeepLink에 저장 → 앱이 열리면서 웹쪽에서 WEB_MSG.READY 메시지 보냄 → 아래 코드 실행

if (pendingDeepLink) {
  webviewRef.current?.postMessage(JSON.stringify(pendingDeepLink));
}
const useDeepLink = (webviewRef: React.RefObject<WebView>, type: string) => {
  const isWebviewReadyRef = useRef<boolean>(false);

  const {
    lastHandledId,
    setLastHandledId,
    pendingDeepLink,
    setPendingDeepLink,
  } = useDeepLinkStore();

  const handleDeepLinkFromNotification = (deepLinkData: any) => {
    if (!deepLinkData || !deepLinkData.target) return;

    // 이미 처리된 딥링크 무시
    if (lastHandledId === deepLinkData.target) return;

    // 즉시 lastHandledId 설정하여 중복 처리 방지
    setLastHandledId(deepLinkData.target);

    const message = {
      type: APP_MSG.DEEP_LINK,
      data: {
        type: deepLinkData.type,
        target: deepLinkData.target,
      },
    };

    if (isWebviewReadyRef.current) {
      webviewRef.current?.postMessage(JSON.stringify(message));
      setPendingDeepLink(null);
    } else {
      // 앱 꺼져있을 경우
      setPendingDeepLink(message);
    }
  };
  
  
  // 현재 탭(type)에 맞는 딥링크 처리 함수를 global 객체에 등록
  useEffect(() => {
    (global as any)[`handleDeepLinkFromNotification_${type}`] =
      handleDeepLinkFromNotification;
    return () => {
      delete (global as any)[`handleDeepLinkFromNotification_${type}`];
    };
  }, [type, lastHandledId]);

  return {
    pendingDeepLink,
    setIsWebviewReady: (ready: boolean) => {
      isWebviewReadyRef.current = ready;
    },
  };
};
  // home탭
  const { pendingDeepLink, setIsWebviewReady } = useDeepLink(
    webviewRef,
    'home',
  );
  
  // order탭
   const { setIsWebviewReady } = useDeepLink(webviewRef, 'order');

  // myPage탭
   const { setIsWebviewReady } = useDeepLink(webviewRef, 'myPage');

  // setting탭
   const { setIsWebviewReady } = useDeepLink(webviewRef, 'setting');

전역함수여야 하는 이유

푸시 알림은 _layout.tsx처럼 앱 최상위에서 수신함

여기서는 단순히 알림이 왔고, 딥링크데이터가 뭔지 정도만 확인 가능함

하지만 딥링크 실행 위치는 각 탭임. 그러나 _layout.tsx는 각 탭의 WebView ref를 직접 알지못함.

즉 알림은 위에서 받는데 실행은 아래서 해야하기 때문에 전역에 함수를 등록해놓는거임

각 탭은 자기 webview ref를 알고 있으니까 전역에 자기 전용함수를 등록해놓는거임.

(global as any)[`handleDeepLinkFromNotification_Home`] = (deepLinkData) => {
  homeWebviewRef.current?.postMessage(JSON.stringify(deepLinkData));
};

layout.tsx는 알림이 오면 현재 탭 이름 확인하고, 전역에서 `handleDeepLinkFromNotification${currentTab}`를 꺼내 실행

탭마다 해야되는 이유

탭마다 독립적인 웹뷰를 가지고 있어서 알림 클릭 시 어떤 탭의 웹뷰로 메세지를 보낼지 구분해야됨

근데 만약 탭마다 구분안하고 공통으로 써버리면, 마지막탭에서 등록된 함수가 덮어씌워버림

ex)

  • 처음에 Home 탭이 실행되면 → Home의 ref로 등록됨
  • 나중에 MyPage 탭이 실행되면 → Home의 ref는 사라지고 MyPage ref로 덮어쓰기됨

홈탭이 열려있어도 딥링크가 mypage로 가버림

앱이 완전히 꺼져있을 때 딥링크가 작동하지 않았던 이유

웹뷰가 아직 준비되지 않았는데 postMessage를 보내버려서 작동하지 않았었음.

웹뷰 준비 전이면 ref객체에 딥링크정보 저장해둠으로써 해결

홈탭에서 딥링크메세지를 받고 처리했는데 다른탭 클릭 시 해당 딥링크가 또 실행됨

알림 응답 객체(NotificationResponse)는 한번 쓰고 사라지지 않고, 앱이 살아있는 동안 유지됨

새 listener가 등록되면, Expo가 이 객체를 또 전달함

앱이 꺼져있는 상태에서 알림이 도착 → 알림 클릭 → 앱 실행. 홈탭 실행 → 딥링크 페이지 이동→

다시 다른 탭 클릭 시 딥링크 페이지 또 이동 → 무한반복

다른탭의 웹뷰 로드되면서 useDeepLink("MyPage") 훅이 자기 전용 핸들러 등록
여전히 남아있는 알림 응답 객체 때문에 listener가 다시 불림

그래서 중복처리 해줘야함

FCM(Firebase Cloud Messaging)

구글에서 제공하는 푸시 알림 인프라

  • 서버 → Firebase → 각 기기(안드로이드/iOS)로 메시지 전달.
  • Expo에서는 expo-notifications 라이브러리를 통해 FCM을 사용.

푸시 알림 클릭 시 앱의 특정 화면으로 이동하게 해주는 데이터.

알림에는 단순히 텍스트말고도 data에 { type, target } 같이 정보를 담아 보냄.

  • 예: { type: 'ORDER', target: '12345' } → 해당 주문 상세 페이지 열기

Firebase 콘솔

Firebase 프로젝트 생성

안드로이드 앱 등록 google-services.json 다운로드

iOS 앱 등록 GoogleService-Info.plist 다운로드

Expo 설정

expo-notifications, expo-device, expo-constants 설치

app.json에 FCM 관련 설정 추가 (Expo가 빌드 시 포함시켜줌)

앱실행 ~ 푸쉬알림 전송 과정

앱에서 FCM 토큰을 발급(registerForPushNotificationsAsync), 기기정보 수집(getDeviceInfo)

↓

사용자 로그인 시 백엔드에게 전달(sendNotificationToken)

↓

백엔드에서는 이벤트 발생 시 특정 혹은 전체 사용자에 대해 FCM 서버에 요청보냄

↓

FCM 서버는 안드로이드 기기에는 직접 전달 iOS 기기에는 APNs를 거쳐서 전달

앱에서 FCM 토큰을 발급(registerForPushNotificationsAsync)

FCM 토큰을 expo-notifications로 발급받음

이때 EAS 프로젝트 ID가 필요하기 때문에 expo-constants로 설정값을 읽어서 같이 넘겨줌

이 토큰을 알아야 서버가 특정 사용자 기기만 골라서 알림을 보낼 수 있음

export async function registerForPushNotificationsAsync() {
  let token;

  if (Platform.OS === 'android') {
    await 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') {
      console.log('Failed to get push token for push notification!');
      return;
    }

    token = await Notifications.getExpoPushTokenAsync({
      projectId: Constants.expoConfig?.extra?.eas?.projectId,
    });
  } else {
    console.log('Must use physical device for Push Notifications');
  }

  return token;
}

기기정보 수집(getDeviceInfo)

expo-device 로 디바이스 정보 가져옴

// 기기 정보 가져오기
export async function getDeviceInfo() {
  const deviceType = await Device.getDeviceTypeAsync();
  const deviceName = Device.deviceName;
  const osName = Device.osName;
  const osVersion = Device.osVersion;
  const brand = Device.brand;
  const manufacturer = Device.manufacturer;
  const modelName = Device.modelName;

  // 고유 식별자 생성
  const uniqueId = `${brand}-${modelName}-${osVersion}-${deviceName}`
    .replace(/\s+/g, '-')
    .toLowerCase();

  return {
    installationId: uniqueId,
    deviceName,
    deviceType,
    osName,
    osVersion,
    brand,
    manufacturer,
    modelName,
  };
}

사용자 로그인 시 백엔드에게 전달(sendNotificationToken)

이제 로그인 할때 fcmToken이랑 deviceInfo를 백엔드에게 보내줌

export const sendNotificationToken = async (body: {
  fcmToken: string;
  deviceInfo: string;
}) => {
  return await baseInstance.post('/api/notification/token', body);
};

그럼 백엔드에서 FCM API를 호출해서 앱기기로 알림을 전달함

푸쉬 알림 수신 과정

알림이 도착했을 때 실행할 함수(onNotificationReceived),
알림을 클릭했을 때 실행할 함수(onNotificationResponseReceived)

export function setupNotificationListeners(
  onNotificationReceived: (notification: Notifications.Notification) => void,
  onNotificationResponseReceived: (
    response: Notifications.NotificationResponse,
  ) => void,
) {

  // 안드로이드 cold-start
	Notifications.getLastNotificationResponseAsync().then((lastResponse) => {
	  if (lastResponse) {
	    onNotificationResponseReceived(lastResponse);
	  }
	});
	
  const notificationListener = Notifications.addNotificationReceivedListener(
    onNotificationReceived,
  );
  
  const responseListener =
    Notifications.addNotificationResponseReceivedListener(
      onNotificationResponseReceived,
    );

  return () => {
    Notifications.removeNotificationSubscription(notificationListener);
    Notifications.removeNotificationSubscription(responseListener);
  };
}
// app/_layout.tsx
const unsubscribe = setupNotificationListeners(
  (notification: any) => {
    console.log('알림 수신:', notification.request.content.data);
  },
  (response: any) => {
    console.log('알림 클릭:', response.notification.request.content.data);

    const deepLinkData = extractDeepLinkData(response);

    if (deepLinkData) {
      const currentTabDeepLinkHandler = (global as any)[
        `handleDeepLinkFromNotification_${currentTab}`
      ];
      if (currentTabDeepLinkHandler) {
        currentTabDeepLinkHandler(deepLinkData);
      } else {
        console.log(
          `${currentTab} 탭의 딥링크 처리 함수가 등록되지 않았습니다.`,
        );
      }
    } else {
      console.log('딥링크 데이터가 없습니다.');
    }
  },
);

앱이 켜져있을 때(Foreground)

onNotificationReceived 실행됨

사용자가 알림 클릭 시 onNotificationResponseReceived 실행됨

앱이 백그라운드에 있을 때

알림은 OS가 표시

onNotificationReceived 실행X

사용자가 알림 클릭 시 onNotificationResponseReceived 실행됨

앱이 완전히 꺼져있을 때

iOS

알림은 OS가 표시

onNotificationReceived 실행X

사용자가 알림 클릭 시 onNotificationResponseReceived 실행됨

Android
알림은 OS가 표시

onNotificationReceived 실행X

사용자가 알림 클릭 → 앱 실행되지만, listener(onNotificationResponseReceived)는 자동 실행되지 않음

아래 이 코드를 호출해서 마지막 알림 응답을 수동으로 꺼내와야 함

Notifications.getLastNotificationResponseAsync().then((lastResponse) => {
  if (lastResponse) {
    onNotificationResponseReceived(lastResponse);
  }
});

알림이 도착했을때 실시간 UI반영이 필요없다면 onNotificationReceived 는 없어도됨.(마켓D도 필요없음)

딥링크

푸쉬알림을 클릭했을 때

백엔드에서 보내준 데이터를 받아 현재 탭의 웹뷰에 postMessage로 전달해서 해당 페이지를 열도록 해야됨

푸쉬알림 클릭

↓

extractDeepLinkData 로 데이터 뽑아냄

↓

현재 탭에 맞는 딥링크처리함수를 전역객체(global)에서 꺼내옴 ex)handleDeepLinkFromNotification_Home

↓

해당 함수 실행 → useDeepLink 내부의 handleDeepLinkFromNotification 동작

↓

이미 처리된 알림(lastHandledId)인지 검사. (홈탭일때 이미 처리한 알림인데 다른 탭에서

새로운 알림이면 메세지 객체 생성

↓

WebView 준비 여부 확인

준비됨(앱이 백그라운드에 있거나, 켜져있을 경우) → webviewRef.postMessage() 로 곧바로 전달

준비 안 됨(앱이 완전히 꺼져있을 경우) → pendingDeepLink에 임시 저장

↓

WebView가 준비 완료 신호(setIsWebviewReady(true)) 보내면 pendingDeepLink 실행

앱이 켜져있거나, 백그라운드에 있을때

앱이 켜져있으니, 웹뷰도 이미 READY 상태 → 푸쉬알림 클릭 →handleDeepLinkFromNotification_home 호출 → isWebviewReadyRef.current === true 라서 바로 웹뷰로 postMessage 전송

앱이 꺼져있을때

푸쉬알림 클릭 → 앱실행 →앱코드에서 useDeepLink 실행 → 아직 READY 상태 아님 → pendingDeepLink에 저장 → 앱이 열리면서 웹쪽에서 WEB_MSG.READY 메시지 보냄 → 아래 코드 실행

if (pendingDeepLink) {
  webviewRef.current?.postMessage(JSON.stringify(pendingDeepLink));
}
const useDeepLink = (webviewRef: React.RefObject<WebView>, type: string) => {
  const isWebviewReadyRef = useRef<boolean>(false);

  const {
    lastHandledId,
    setLastHandledId,
    pendingDeepLink,
    setPendingDeepLink,
  } = useDeepLinkStore();

  const handleDeepLinkFromNotification = (deepLinkData: any) => {
    if (!deepLinkData || !deepLinkData.target) return;

    // 이미 처리된 딥링크 무시
    if (lastHandledId === deepLinkData.target) return;

    // 즉시 lastHandledId 설정하여 중복 처리 방지
    setLastHandledId(deepLinkData.target);

    const message = {
      type: APP_MSG.DEEP_LINK,
      data: {
        type: deepLinkData.type,
        target: deepLinkData.target,
      },
    };

    if (isWebviewReadyRef.current) {
      webviewRef.current?.postMessage(JSON.stringify(message));
      setPendingDeepLink(null);
    } else {
      // 앱 꺼져있을 경우
      setPendingDeepLink(message);
    }
  };
  
  
  // 현재 탭(type)에 맞는 딥링크 처리 함수를 global 객체에 등록
  useEffect(() => {
    (global as any)[`handleDeepLinkFromNotification_${type}`] =
      handleDeepLinkFromNotification;
    return () => {
      delete (global as any)[`handleDeepLinkFromNotification_${type}`];
    };
  }, [type, lastHandledId]);

  return {
    pendingDeepLink,
    setIsWebviewReady: (ready: boolean) => {
      isWebviewReadyRef.current = ready;
    },
  };
};
  //home탭
  const { pendingDeepLink, setIsWebviewReady } = useDeepLink(
    webviewRef,
    'home',
  );
  
  //order탭
   const { setIsWebviewReady } = useDeepLink(webviewRef, 'order');

  //myPage탭
   const { setIsWebviewReady } = useDeepLink(webviewRef, 'myPage');

  //setting탭
   const { setIsWebviewReady } = useDeepLink(webviewRef, 'setting');

전역함수여야 하는 이유

푸시 알림은 _layout.tsx처럼 앱 최상위에서 수신함

여기서는 단순히 알림이 왔고, 딥링크데이터가 뭔지 정도만 확인 가능함

하지만 딥링크 실행 위치는 각 탭임. 그러나 _layout.tsx는 각 탭의 WebView ref를 직접 알지못함.

즉 알림은 위에서 받는데 실행은 아래서 해야하기 때문에 전역에 함수를 등록해놓는거임

각 탭은 자기 webview ref를 알고 있으니까 전역에 자기 전용함수를 등록해놓는거임.

(global as any)[`handleDeepLinkFromNotification_Home`] = (deepLinkData) => {
  homeWebviewRef.current?.postMessage(JSON.stringify(deepLinkData));
};

layout.tsx는 알림이 오면 현재 탭 이름 확인하고, 전역에서 `handleDeepLinkFromNotification${currentTab}`를 꺼내 실행

탭마다 해야되는 이유

탭마다 독립적인 웹뷰를 가지고 있어서 알림 클릭 시 어떤 탭의 웹뷰로 메세지를 보낼지 구분해야됨

근데 만약 탭마다 구분안하고 공통으로 써버리면, 마지막탭에서 등록된 함수가 덮어씌워버림

ex)

  • 처음에 Home 탭이 실행되면 → Home의 ref로 등록됨
  • 나중에 MyPage 탭이 실행되면 → Home의 ref는 사라지고 MyPage ref로 덮어쓰기됨

홈탭이 열려있어도 딥링크가 mypage로 가버림

앱이 완전히 꺼져있을 때 딥링크가 작동하지 않았던 이유

웹뷰가 아직 준비되지 않았는데 postMessage를 보내버려서 작동하지 않았었음.

웹뷰 준비 전이면 ref객체에 딥링크정보 저장해둠으로써 해결

홈탭에서 딥링크메세지를 받고 처리했는데 다른탭 클릭 시 해당 딥링크가 또 실행됨

알림 응답 객체(NotificationResponse)는 한번 쓰고 사라지지 않고, 앱이 살아있는 동안 유지됨

새 listener가 등록되면, Expo가 이 객체를 또 전달함

앱이 꺼져있는 상태에서 알림이 도착 → 알림 클릭 → 앱 실행. 홈탭 실행 → 딥링크 페이지 이동→

다시 다른 탭 클릭 시 딥링크 페이지 또 이동 → 무한반복

다른탭의 웹뷰 로드되면서 useDeepLink("MyPage") 훅이 자기 전용 핸들러 등록
여전히 남아있는 알림 응답 객체 때문에 listener가 다시 불림

그래서 중복처리 해줘야함

profile
Born to be FE developer 🧑🏻‍💻

0개의 댓글