[React Native] deeplink를 이용해 알림을 여는 방법(with. notifee)

profile image pIutos 2024. 1. 3. 21:25

들어가기 전에

딥링크(deeplink)는 특정 페이지에 도달 할 수 있는 링크를 말합니다. 딥링크를 이용하여 React Native 어플리케이션의 특정 페이지에 도달시킬 수 있습니다. 예를들어 'app://targetPage' 링크를 딥링크로 열게되면 targetPage가 표시됩니다.

구현 요구사항

우선 아래와 같이 notifee를 이용하여 서버에서 보내는 데이터인 notifee.data 영역에 deepLinkUrl을 담습니다.

이후 알림을 전송하고, 앱에서 알림을 받으면 해당 deeplink url을 이용하여 앱을 열어야 하도록 구현합니다. 하지만 이런 방식은 레퍼런스가 없어 기능을 직접 구현하였고,지금부터 구현한 방법을 살펴보겠습니다.

"data": {
  "notifee": {
    "id": "1",
    "title": "공지사항을 확인해 보세요!",
    "subtitle": "학사공지",
    "body": "서울시립대학교 대학원 시행세칙 및 통합대학원 학칙 일부개정(안) 사전예고",
    "data": {
      "deepLinkUrl": "uoslife://announcement/detail/123"
    },
    // ...

 

프로젝트에 deep link 설정하기

세팅되어있는 React Native 프로젝트에서 아래 명령어를 사용하면 플랫폼 별로 알맞게 deep linking 환경 설정이 됩니다. 자세한 사항은 해당 공식 문서를 참조해주세요.

npx uri-scheme add 'deeplink 프로젝트이름' --ios
npx uri-scheme add 'deeplink 프로젝트이름' --android

screen에 따라 deepLink url 설정하기

const DEEPLINK_PREFIX_URL = ['uoslife://'];

const deepLinksConfig = {
  initialRouteName: 'Main',
  screens: {
    Main: {
      initialRouteName: 'MainTab',
      screens: {
        // ...
      },
    },
    Library: 'library',
    Announcement: {
      initialRouteName: 'AnnouncementMain',
      screens: {
        AnnouncementMain: 'announcement',
        AnnouncementDetail: 'announcement/detail/:id',
        // ...
      },
    },
  },
};

const linking: LinkingOptions<RootStackParamList> = {
  prefixes: DEEPLINK_PREFIX_URL,
  config: deepLinksConfig,
  async getInitialURL() {
    // 딥링크를 이용해서 앱이 오픈되었을 때
    const url = await Linking.getInitialURL();
    if (url != null) return url;
  },
};

이후 해당 linking config를 NavigationConatiner에 넘겨줍니다.

<NavigationContainer linking={linking} ...></NavigationContainer>

앱 알림을 클릭했을 때 딥링크 열기

이제 앱에 deeplink설정을 완료했으니 원하는 동작인 앱 알림을 클릭했을 때 해당 딥링크를 여는 코드를 작성해보겠습니다.

앱이 Foreground 상태일 때

deeplink는 react-native의 Linking.openURL에 url을 넣으면 열 수 있습니다.

notifee의 onForegroundEvent 메서드에 클릭 시 딥링크를 여는 onPressEvent를 추가하는 방식으로 구현합니다.

import { Linking } from 'react-native';
import notifee, { Event } from '@notifee/react-native';

// ...

static async onPressEvent({ type, detail }: Event): Promise<void> {
    if (type !== EventType.PRESS) return;
    const {notification} = detail;
    if (!notification || !notification.data || !notification.data.deepLinkUrl)
      return;
    await Linking.openURL(notification.data.deepLinkUrl as string);
  }

static onForegroundEvent(): () => void {
  return notifee.onForegroundEvent(
    async event => await this.onPressEvent(event),
  );
}

이후 해당 onForegroundEvent 메서드를 App.tsx파일에서 실행시킵니다.

// App.tsx
useEffect(() => {
  NotificationService.onForegroundEvent();
}, []);

앱이 Background 상태일 때

백그라운드 상태일 때는 앱에 백그라운드 이벤트 이를 위해 notifee.getInitialNotification() 메서드를 사용합니다.

getInitialNotification
: This API can be used to fetch which notification & press action has caused the application to open.

앱을 열었을 때 해당 메서드를 이용하여 press action이 되어 들어왔는지 확인하고, 있다면 해당 notification data에서 deepLinkUrl을 이용해 딥링크를 열게 하는 동작을 구현하면 됩니다.

이에 따라 처음 구현은 아래와 같았습니다.

// App.tsx
useEffect(() => {
  (async () => {
    const initialNotification = await notifee.getInitialNotification();
    if (initialNotification) {
      const {data} = initialNotification.notification;
      if (!data || !data.deepLinkUrl) return;
      await Linking.openUrl(data.deepLinkUrl);
    }
  })();
}, [])

하지만 앱 초기로딩 시 로그인 상태를 변경하는 과정에서 문제가 발생했습니다.

TroubleShooting: 로그인 되지 않은 상태에서 발생하는 문제

React Native Navigation에서는 로그인 상태를 처리하기 위해 아래 방식과 같이 isLoggedIn 상태에 따라 각기 다른 화면을 렌더링합니다.

<Stack.Navigator ...>
  {isLoggedIn ? (
    <>
      <Stack.Screen name="Main" />
      <Stack.Screen name="Mypage" />
      // ...
      />
    ) : (
    <>
      <Stack.Screen name="Account" />
      // ...
    </>
  )}
</Stack.Navigator>

이때 isLoggedIn은 초기값이 false이기 때문에 앱 초기 로딩 과정에서 로그인 여부를 파악할 때 까지 Account 화면이 표시됩니다.

이 상황에서 만약  '~://main' 으로 deepLink 요청이 온다면 Main 페이지는 실제로 렌더링 되지 않았기 때문에 아래과 같은 오류가 발생했습니다.

따라서 로그인 로직 이후 deeplink url을 열어줘야 했고, 이 문제를 전달받은 url을 storage에 저장해서 넘겨주는 방식으로 해결했습니다.

1. 'openedDeepLinkUrl' key에 notifee에서 받은 deepLinkUrl을 저장합니다.

const linking = {
  prefixes: DEEPLINK_PREFIX_URL,
  config: deepLinksConfig,
  async getInitialURL() {
    // 딥링크를 이용해서 앱이 오픈되었을 때
    const url = await Linking.getInitialURL();

    if (url != null) return url;

    // 백그라운드에서 알림 클릭 시 deepLink가 있는 경우 해당 url을 storage에 저장
    const initialNotification = await notifee.getInitialNotification();
    if (!initialNotification) return null;
    const {data} = initialNotification.notification;
    if (!data || !data.deepLinkUrl) return null;
    storage.set('openedDeepLinkUrl', data.deepLinkUrl as string);
  },
};

2. 이후 앱 로딩과 로그인이 완료된 이후, 해당 key를 가져와 딥링크를 open합니다.

// ...set app loading and login status true
const openedDeepLinkUrl = storage.getString('openedDeepLinkUrl');
if (openedDeepLinkUrl) {
  await Linking.openURL(openedDeepLinkUrl);
  storage.delete('openedDeepLinkUrl');
}

이렇게 구현하면 앱 로딩이 완료된 상태, 그리고 로그인이 완료된 상태에서만 deeplink가 열리게 되므로 유저가 느끼는 동작에도 문제가 없고, 상기 에러가 발생하지 않았습니다.

github의 react-navigation에도 해당 issue가 오픈되어 있는데, 로그인 상태에서 딥링크 공식적인 메뉴얼이 나와있지 않았기에 여러 방법이 제시되어 있었고 저는 위와 같은 방법을 제시했습니다.

정리하기

지금까지 알림을 deepLink로 열기 위해 react native에 딥링크 환경설정을 하고, 앱이 기기에서 foreground일 때와 background 상태일 때의 코드를 각각 작성하여 알림을 열었을 때 해당 deeplink url로 이동하는 동작을 구현했습니다.

결과적으로 구현 요구사항과 일치하게 동작이 잘 되는 모습을 볼 수 있습니다.

좌: foreground 환경, 우: background 환경

 

문제가 있는 부분이 있거나, 이해가 되지 않는 부분이 있다면 댓글로 남겨주세요.

+) TroubleShooting2: 뒤로가기가 되지 않는 문제

nested된, 즉 여러 화면 내부에 존재하는 화면에서 뒤로가기 동작시 아래와 같은 에러가 발생했습니다.

// nested screen
const handlePressBackButton = () => {
  navigation.goBack();
};

return (
  <Header label={label} onPressBackButton={handlePressBackButton} />
  // ...
);

linking config에 아래와 같이 initialRouteName을 추가하면 해당 에러가 발생하지 않습니다.

const deepLinksConfig = {
  initialRouteName: 'Main', // 추가
  screens: {
    Main: {
      initialRouteName: 'MainTab', // 추가
      screens: {
        // ...
      },
    },