Expo에서 React Navigation과 DeepLink

햄스터아저씨·2021년 9월 13일
3

참고:
1. https://reactnavigation.org/docs/5.x/deep-linking
2. https://reactnavigation.org/docs/5.x/navigation-container

우선 DeepLink는 기본적으로 2개의 시나리오를 가지고 있습니다.
1. 앱이 열리지 않은 상태인 경우, 링크 기반으로 초기상태 설정하기
2. 앱이 이미 열린 상태인 경우, 수신된 링크를 기반으로 상태를 업데이트

Expo Project에서는 app.json에 아래와 같이 scheme을 작성하여 url을 만들 수 있습니다.

{
  "expo": {
    "scheme": "mychat"
  }
}

단 모든경우에 적용되는 것은 아니며

  • 프로덕션 모드에서만 mychat:// 형태로 작동가능하고
  • 개발모드인 Expo Go 에서는 exp://ip:19000 형태로 지원됩니다.

이제 아래 코드를 그대로 넣어봅시다.

import * as Linking from 'expo-linking';

const prefix = Linking.createURL('/');

function App() {
  const linking = {
    prefixes: [prefix],
  };

  return (
    <NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
      {/* content */}
    </NavigationContainer>
  );
}

이 때 Linking.createURL('/');를 하는 이유는 모드에 따라 url이 다르기 때문입니다.

  • 프로덕션모드에서는 mychat://~~~ 형태로 저장되며
  • 개발 모드에서는 exp://ip:19000/~~~ 형태로 저장됩니다.

2. 맵핑 설정: URL Path -> Component

이제 어떤 path가 호출될 때, 어떤 Component로 연결할지 맵핑하는 정보를 만듭니다.
맵핑정보는 NavigationContainer가 가지게 됩니다.

참고:
1. https://reactnavigation.org/docs/5.x/configuring-links/

NavigationContainerlinking 프로퍼티는 아래 2개 항목을 반드시 포함해야 합니다.

  • prefixes
    보통 Linking.createURL("/") 로 만들어진 값을 그대로 이용합니다.
    웹에서 바로 앱으로 연결하고 싶은 경우, 추가적으로 https 도메인을 배열에 추가하면 됩니다.
  • config
    여기서 pathcomponent를 맵핑합니다. 라우팅 설정이라고도 합니다.
    전부 맵핑할 필요는 없이 원하는 component만 설정할 수 있습니다.

아래 예시에서는 <Stack.Navigator>를 return하는 HomeStack 내에, <Stack.Screen>ChatRoom 으로 path를 설정할때의 config의 형태를 보여줍니다.

App.tsx

const prefix = Linking.createURL("/");
...

  return (
    ...
    <NavigationContainer
      linking={{
        prefixes: [prefix],
        config: {
          screens: { 
            // HomeStack 내에 포함된 components 중 path를 부여하고 싶은 것만 작성합니다.
            // :roomId 에 들어가는 값은 navigation의 params로 전달됩니다.
            ChatRoom: "room/:roomId",
          },
        },
        // ...
      }}}
    >
        <HomeStack />
    </NavigationContainer>
    ...
  )

3. Component 설정: 에서 :roomId 변수받기

위에서 설정한 :roomIdrouteparams 객체를 통해 전달받을 수 있습니다.
ChatRoom.tsx

const ChatRoom = ({ navigation, route }) => {
  console.log(route.params);
  
  //출력 결과  
  Object {
    "roomId": "123",
  }
    

여기까지 오면 화면전환에 필요한 설정은 모두 끝났습니다.
이제 화면전환을 호출하면 설정에 따라 화면이 전환되게 됩니다.

4. <Link>로 URL Path 사용하기

<Link>태그를 이용하면 위에서 설정한 path를 앱 내에서 호출할 수 있습니다.
어떻게 보면 navigation.navigate() 함수를 쓰는것보다 이쪽이 더 간단한 것 같네요.

import { Link } from "@react-navigation/native";
...
        <Link to="/room/123">
          <Label>Go to Jane's profile</Label>
        </Link>

5. 푸시알림에서 딥링크로 사용하기

push 알림을 받은 사용자가 알림을 선택하면, 해당 컨텐츠가 바로 앱에서 열려야겠죠.
그 부분을 구현해봅시다. 여기서는 push알림에 url이 이미 탑재되어있다고 가정합니다.

deep link url이 포함된 푸시알림을 발생시키는 방법은 다음 링크를 참고해주세요.
https://velog.io/@hamster/Expo-Notification-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-Local

기본 개념은 아래와 같습니다.
1. 화면 이동에 React NavigationDeepLink 는 위에서 설정한 방법 외에도 서드파티 모듈을 통합하여 DeepLink 를 지원할 수 있습니다.
별거 아니고 아래 두 함수를 구현하면 됩니다.

  • getInitialURL
  • subscribe
    그러면 incoming link를 오버라이딩 되어 딥링크를 원하는 형태로 조정할 수 있습니다.

expo-notifications 이 여기서 서드파티 부분에 들어갑니다.

import React from 'react';
import { Linking } from 'react-native';
import * as Notifications from 'expo-notifications';
import { NavigationContainer } from '@react-navigation/native';

const prefix = Linking.createURL("/");

export default function App() {
  return (
    ...
    <NavigationContainer
      linking={{
        prefixes: [prefix],
        config: {
          // 여기서 url path와 screen을 맵핑합니다.
          // 원하는 screen만 맵핑하면 됩니다.
          // initialRouteName 에는 앱의 첫 화면 screen 이름을 적어주세요. 최초 화면을 설정하지 않으면, deep link로 들어간 화면이 첫 화면이 되어버려, 앱을 종료할 때까지 home으로 갈 수 없게됩니다.
          initialRouteName: "HomeTab",  
          screens: {
            HomeTab: "room",
            ChatRoom: "room/:roomId",
          },
        },
        async getInitialURL() {
          let url = await Linking.getInitialURL();

          if (url != null) {
            return url;
          }

          const response = await Notifications.getLastNotificationResponseAsync();
          url = response?.notification.request.content.data.url;

          return url;
        }
        subscribe(listener) {
          const onReceiveURL = ({ url }: { url: string }) => listener(url);

          // Listen to incoming links from deep linking
          Linking.addEventListener('url', onReceiveURL);

          // Listen to expo push notifications
          const subscription = Notifications.addNotificationResponseReceivedListener(response => {
            const url = response.notification.request.content.data.url;
            listener(prefix + "room"); // 우선 최초화면으로 먼저 이동합니다. 이렇게 하지 않으면, 변수만 다른(:roomId) 동일한 화면이(ChatRoom) 이미 열려있던 경우, deep link로 인한 화면이동이 발생하지 않습니다.
            listener(url); // 원하는 화면으로 이동합니다.
          });

          return () => {
            // Clean up the event listeners
            Linking.removeEventListener('url', onReceiveURL);
            subscription.remove();
          };
        },
      }}>
      {/* Your app content */}
    </NavigationContainer>
  );
}

참고: https://docs.expo.dev/versions/latest/sdk/notifications/#handling-push-notifications-with-react-navigation

profile
서버도 하고 웹도 하고 시스템이나 인프라나 네트워크나 그냥 다 함.

1개의 댓글

comment-user-thumbnail
2022년 11월 6일

정말 감사합니다!

답글 달기