searchgithubemail

React Native Deep Linking (2)

React Native Deep Linking 연결

2025-02-03

딥링크

딥링크 유형

post image

출처 tosspayments

딥링크 구현

1. 웹에서 앱으로 이동

const useAppOpen = () => {
  const [appOpened, setAppOpened] = useState(false);
  const { os } = useFetchCookie();
  const router = useRouter();

  useVisibilityChange({
    onVisible: () => setAppOpened(true),
    onHidden: () => setAppOpened(false),
  });

  const handleAppOpen = ({ appScheme }: { appScheme: string }) => {
    router.replace(
      os === 'ios'
        ? `${APP_SCHEME}${appScheme}`
        : `intent://${appScheme}#Intent;scheme=myapp;package=com.myapp.app;end;`,
    );

    setTimeout(() => {
      if (!appOpened) {
        window.location.href =
          os === 'ios' ? APPLE_STORE_URL : GOOGLE_PLAY_STORE_URL;
      }
    }, 500);
  };

  return {
    handleAppOpen,
  };
};

export default useAppOpen;

웹에서는 우선 os를 저장했고, os별로 다른 경로를 지정했다.

ios 에서는 myapp://로 바로 Deep Link를 이용해 이동하고, aos에서는 intent://로 시작하는 Intent스킴을 사용해 서로 다른 경로를 사용하고 있다.

그리고 딥링크에 앱이 열리지 않으면 setTimeout을 이용해 앱스토어로 이동하도록 구현했다.

setTimeout을 사용한 이유는 앱이 설치되어 있다면, 브라우저는 즉시 앱을 실행하고, 반대로 앱이 설치되지 않으면 아무런 변화도 일어나지 않는다. 이 상태를 감지하기위해서 500ms의 시간동안 앱이 실행되지 않는다면 앱스토어로 이동하는 방식으로 구현했다.

2. 앱에서 링크 이동

AOS

// android/app/src/main/AndroidManifest.xml

    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="myapp" />
    </intent-filter>

위 경로에서 schememyapp로 지정해주면 된다.

IOS

// ios/MyApp/Info.plist

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLName</key>
        <string>com.myapp.app</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>myapp</string>
        </array>
    </dict>
</array>

CFBundleURLSchemesmyapp을 추가해주면 된다.


react-navigation

현재 프로젝트에서는 @react-navigation/native 라이브러리를 사용하고 있어서, NavigationContainer에서 지원하는 linking props사용해 딥링크를 구현했다.

// App.tsx

{...}
<NavigationContainer
  linking={linkingConfig}
  ref={navigationRef}
  {...}
>
    {...}
</NavigationContainer>
{...}

// ../linkingConfig.ts

export const linkingConfig: LinkingOptions<NavigationProps> = {
  prefixes: ['myapp://'],
  config: {
    screens: {
      [RootNavigatorName.rootTab]: {
        screens: {
          [TabNavigatorName.HOME_TAB]: {
            path: 'open',
            screens: {
              [StackNavigatorName.HOME_SCREEN]: '',
            },
          },
          [TabNavigatorName.ECOMMERCE_TAB]: {
            path: 'ecommerce',
            screens: {
              [StackNavigatorName.ECOMMERCE_SCREEN]: '',
            },
          },
        },
      },
      [StackNavigatorName.AUTH_ONBOARDING_SCREEN]: 'onboarding',
      [StackNavigatorName.AUTH_SCREEN]: 'auth',
    },
  },
  {...}
};

먼저 prefixesmyapp://을 추가해주고, screens에 각 스크린에 등록한 경로들을 path로 지정해주면 된다.

  async getInitialURL() {
    const url = await Linking.getInitialURL();

    if (url) {
      const redirectUrl = await handleGetUserInfo();
      return redirectUrl || url;
    }
  },

getInitialURL을 이용해 앱이 처음 실행될 때, 딥링크를 처리할 수 있도록 구현했다. 여기서 handleGetUserInfo 함수는 앱이 딥링크를 통해 진입했을 경우 스플래쉬스크린을 거치지않아, 사용자 정보를 가져와 로그인 여부를 확인하는 함수이다.

  subscribe(listener) {
    const onReceiveURL = async (event: { url: any }) => {
      if (event.url) {
        const redirectUrl = await handleGetUserInfo();
        listener(redirectUrl || event.url);
      }
    };

    const linkingListener = Linking.addEventListener('url', onReceiveURL);
    const appStateListener = AppState.addEventListener('change', async nextAppState => {
      if (nextAppState === 'active') {
        const url = await Linking.getInitialURL();

        if (url) {
          const redirectUrl = await handleGetUserInfo();
          listener(redirectUrl || url);
        }
      }
    });

    return () => {
      linkingListener.remove();
      appStateListener.remove();
    };
  },

subscribe를 이용해 앱이 실행중일 때, 딥링크를 처리할 수 있도록 구현했다.