Firebase Dynamic Links 사용해 보기

Firebase Dynamic Links Documentation

URL Scheme / Deeplink 설정 및 작동 방식 이해하기

firebase_core | Flutter Package
firebase_dynamic_links | Flutter Package
uni_links | Flutter Package
http | Dart Package

Firebase 세팅하기 - Flutter 3.0 이후
Firebase 세팅하기 - Flutter 3.0 이전
URL Scheme & Deeplink 설정 및 작동 방식 이해해보기

이번 글에서는 오랜 시간에 걸쳐 작성한 다이나믹 링크라는 파이어베이스 기능에 대해서 다뤄볼 예정이다.

아무래도 Firebase의 기능에 대한 사용 방법을 중점으로 다루다 보니 범용 링크를 다르게 사용하는 방법에 대해서는 설명하지 않고 다이나믹 링크에 대해서만 작성하였다.

다이나믹 링크가 무엇이고 ? 무슨 기능을 가지고 있을까 ? 다이나믹 링크에 대해서 알아보기 전에 앱들 간의 통신을 어떻게 하는지에 대해서 먼저 알아야한다.

Android 경우에는 앱 간 통신을 자주 하지는 않지만 IOS는 실행 중인 앱 위에 다른 앱을 띄우지 않는 정책으로 인해 앱들 간의 통신을 통해서 앱을 자동으로 열어주는 것을 경험해봤을 것이다.

이 때 앱을 어떻게 열어주게 될까 ? 바로 등록된 deeplink를 통해서 앱을 작동시켜 주는 것이다. 네이버, 카카오와 같은 SDK를 사용하려고 개발자 센터에 등록을 하려고 하면 앱내에 deeplink를 등록하라고 한다. 카카오의 경우 "kakao{NATIVE_KEY}" 형식으로 딥링크를 넣어주라고 하는데, 카카오에서 버튼에 해당 스킴을 등록하여 버튼이 클릭됬을 때 스킴을 작동시켜 해당 스킴이 등록된 앱을 오픈하기 위한 의도가 있어서다. 앱으로 들어오는 카카오 로그인 스킴을 파싱해보면 kakao{NATIVE_KEY}://oauth2... 형태로 딥링크가 전달된다.

이렇게 앱들 간의 통신이 가능한 것이다. 위에서 설명한 것이 URL Scheme 체계라고 하는데, 이 방식에는 문제가 있다. 바로 앱에서 임의로 등록이 가능하기에 고유하지 않고 검증되지 않았다는 문제가 있다.

종종 시스템 바텀 시트가 올라오면서 여러 앱 중 선택할 수 있게 노출을 할 때가 있는데, 그 이유가 같은 딥링크가 등록되어 있어서다. 만약에 딥링크가 등록되어 있는 앱이 없다면 어떻게 될까? 아무런 행동도 하지 않게 된다. 그리고 이 방식은 범죄, 불법적인 용도로 사용이 가능하여 구글과 애플이 범용 링크를 개발하여 사용하게 한 것이다.

구글의 Android는 App Links, 애플의 IOS에서는 Universal Links라고 한다.
App Links와 Universal Links를 모두 지원하여 플랫폼에 해당하는 링크를 각각 생성하지 않고 하나의 링크로 생성하게 해주는 서비스가 바로 Firebase에서 제공하는 Dynamic Links이다. 양대 플랫폼 사의 링크를 여기서는 그냥 다이나믹 링크라고 하겠다.

다이나믹 링크는 기존 URL Scheme 체계의 단점을 보완하여 나온 새로운 방식이고, 다이나믹 링크는 구글과 애플의 서버에 인증을 받아 사용하여야 하며, 다이나믹 링크의 기능에는 해당 링크의 플랫폼 앱이 설치되어 있지 않을 때에 스토어, 웹 등으로 보내주는 기능도 갖추고 있다. URL Scheme 체계에서 사용하는 tyger:// 방식이 아닌 https:// 도메인 형태로 사용이 된다.

다이나믹 링크에 대해서는 추가적으로 개발을 하면서 좀 더 자세히 알아보자.

Firebase

Dynamic Links를 클릭하여 홈으로 들어가보자. 아직 Dynamic Links를 사용하지 않았으면, 아래 이미지처럼 나온다. 시작하기를 눌러보자.

다이나믹 링크에 사용할 도메인을 등록하여야 하는데, 꼭 구매한 도매인을 사용하지 않아도 된다. 드롭다운 박스를 클릭해보면 Google에서 제공하는 도메인이라고 추천 도메인이 나와있다.

도메인을 구성할 때에는 두 가지 방법이 있다. 먼저 추천 도메인을 사용할 수 있고, 개인 생성 도메인을 사용하여도 된다.

기본 제공 도메인을 사용하는 경우에는 https://your_domain.page.link 형태로 생성해 주면 되고, 개인 도메인 사용을 원할 경우 https://share.your_domain 형태로 접두사를 넣어 사용하기를 권장하고 있다.

우리는 기본 제공 도메인을 사용해보자.

도메인을 선택하고 계속을 클릭하면 아래와 같이 도메인 검증을 받고 사용할 수 있다는 확인을 받을 수 있다. 만일 여기서 사용할 수 없는 도메인인 경우 기존 도메인이 다른 플랫폼에 등록되있는 경우일 수 있어서 도메인을 변경해주면 된다.

이제 Dynamic Links 등록이 끝났다.

Dynamic Links 대시보드를 통해서 새 동적 링크를 생성하여 사용할 수도 있고 Flutter에서 생성할 수도 있다. 대시보드 사용 방법은 아래에서 다시 살펴보도록 하자.

추가로 URL 프리픽스는 최대 10개 까지 프로젝트에 사용할 수 있다. 추가를 원하면 URL 프리픽스 추가를 통해서 추가 등록하여 사용하면 된다.

Settings

이제 Firebase Dynamic Links 세팅 및 도메인 등록은 완료했지만, 아직 앱에서는 동적 링크를 받지 못한다.

Flutter는 Android / IOS 플랫폼을 동시에 개발해야 하므로 각각 동적 링크로 앱을 열 수 있도록 앱 설정을 진행해 보자.

Android

안드로이드 세팅은 간단하다. menifest 파일에 스킴을 등록해 주면 끝이다.

아래 경로를 따라서 AndroidMenifest.xml 파일을 열어보자.

android > app > src > main > AndroidMenifest.xml

해당 파일의 intent-filter를 아래와 같이 추가해 주면 된다.

<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="https" 
    	  android:host="{your_domain}.page.link" />
</intent-filter>

태그 추가가 헷갈리신 분들은 반드시 MainActivity가 있는 activity 태그의 자식 태그에 추가해야 한다는 것을 아셔야 한다. Flutter만 기본적으로 사용하시는 프로젝트는 대부분 MainActivity가 하나만 존재하는데, 써드파티 툴을 등록한 프로젝트는 MainActivity가 한 개가 아닐 수도 있다.

 <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            ...
           
            <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="https" 
                      android:host="tyger.page.link" />
		</intent-filter>
        </activity>

Android 세팅은 여기까지만 해주면 된다. 추가적으로 짚고 넘어가야할 문제가 있다.

공식문서 상에는 your_domain.page.link를 호스트로 등록하라고 되어있기는 한데, firebase_dynamic_links 라이브러리르 통한 딥링크 수신이 아닌 uni_links, app_links 또는 써드파티 툴을 사용한 딥링크 리스너 로직을 개발하게 될시에는 저런식으로 등록해주면 구문 해석을 못해 우리가 앱에서 받고자 하는 딥링크가 아닌 다이나믹 링크로 생성한 숏링크 자체가 그대로 들어오게 된다.

이럴 때에는 host 부분을 본인이 사용할 딥링크로 해주면 된다.

예를들어 SNS를 개발한다고 가정하고 Feed를 공유해보자. 이렇게 되면 feed/{feedNo} 형태로 딥링크를 생성하게 될것이다. 아마도 전체 딥링크는 아래와 같다고 가정하자.

https://tyger.com/feed/{feedNo}

이럴 때 호스트는 tyger.com 을 넣어주어야 해석된 딥링크를 받을 수 있다. 물론 firebase_dynamic_links를 통해서 딥링크 수신로직을 개발할거면 상관없다.

IOS

이번에는 IOS에 세팅을 진행하도록 하자.

세팅 전 주의 사항으로 Apple Developer 계정이 없다면 진행할 수 없다. 그리고 IOS 세팅 방법으로 두 가지 방법을 공유할 것인데 이 중 하나만 진행해야 한다는 것이다.

Associated Domains를 추가해주는 세팅만 해주면 된다.

먼저 첫 번째 방법은 직접 Info.plist 파일에 직접 추가해주는 방법이다. 간단해 보이지만 태그안에 잘 못 넣게되면 복잡해진다.

<key>com.apple.developer.associated-domains</key>
   <array>
      <string>applinks:{your_domain}.page.link</string>
   </array>

이번엔 XCode를 통해서 추가하는 방법이다. 우선 XCode를 열자.

Singing & Capabilities 탭으로 이동해서 Associated Domains를 추가하여야 한다.

Associated Domains가 보이지 않는다면 Capablilty를 클릭해서 Associated Domains를 추가해주면 된다.

추가 버튼을 클릭하고 아래 형태로 넣어주면 된다.

applinks:{your_domain}.page.link

여기까지 진행했다면 IOS 설정도 끝이다.

기본 도메인이 아닌 커스텀 도메인을 사용하는 경우에는 아래 태그를 Info.plist에 추가해줘야 사용이 가능하다.

<key>FirebaseDynamicLinksCustomDomains</key>
<array>
   <string>https://{custom_domain}</string>
</array>

Flutter

설정은 끝이 났으니 정상적으로 작동하는지 우선 간단하게 테스트를 진행해보자.

dependencies

Dynamic Links 사용을 위해서 아래의 라이브러리를 추가하자.

dependencies:
	firebase_core: ^2.7.0
	firebase_dynamic_links: ^5.0.15

Dynamic Links의 Short Url을 생성하는 코드이다. 해당 코드를 실행시켜서 Short Url을 출력해보자.

String _yourDomain = "tyger";

DynamicLinkParameters dynamicLinkParams = DynamicLinkParameters(
                uriPrefix: "https://$_yourDomain.page.link",
                link: Uri.parse("https://$_yourDomain.page.link/test"),
                androidParameters: const AndroidParameters(
                  packageName: $_yourPackageName,
                  minimumVersion: 0,
                ),
                iosParameters: const IOSParameters(
                  bundleId: $_yourPackageName,
                  minimumVersion: '0',
                ),
              );
              ShortDynamicLink dynamicLink = await FirebaseDynamicLinks.instance
                  .buildShortLink(dynamicLinkParams);

              String url = dynamicLink.shortUrl.toString();
              logger.e(url);

이제 출력된 Url 주소를 카카오톡 / 메모 등의 공간에 붙여 넣어 주고 링크를 클릭해보자.

앱으로 정상적으로 진입되는 것을 확인했다. 앱은 진입했지만 아직 아무런 이벤트가 발생하지는 않는다.
다이나믹 링크에 실어져 있는 딥링크를 수신하는 기능을 아직 만들지 않아서 그렇다.

딥링크를 앱에서 수신할 수 있도록 기능을 추가해 보자.

이제 본격적으로 다이나믹 링크를 생성해보자. 다이나믹 링크 생성은 Firebase Dynamic Links 대시보드를 통해서 생성할 수도 있고, 앱 자체에서도 생성할 수 있다.

각 생성 방식에 따라 용도가 다를 것이다. 보통 앱에서 생성하는 다이나믹 링크는 생성시마다 딥링크가 변해야 하는 경우일 것이고, 대시보드에서 생성하는 다이나믹 링크는 마켓팅 캠페인에 사용이 될 것이기 때문이다.

추가로 앱에서 생성하는 다이나믹 링크는 대시보드에서 노출되지 않기 때문에 상황에 맞는 링크 생성이 필요하다.

Firebase

Dynamic Links 대시보드에서 다이나믹 링크를 생성해보자.

먼저 새 동적링크를 클릭하여 만들기 페이지로 들어오자. 보면 URL 프리픽스를 원하는 값으로 생성할 수 있는데, 기본 제공 값으로 사용하는게 편하다.

동적 링크를 설정하라고 하는데 여기서 딥 링크 URL 부분이 중요하다. 이 부분에 생성하는 딥링크가 앱에 진입했을 때 수신 받는 딥링크이고, 해당 링크로 앱의 이벤트를 결정하게 된다.
동적 링크 이름은 자유롭게 생성하자.

Apple 세팅 부분은 애플 개발자 계정이 있는 경우에만 등록이 가능하고, 개발자 계정이 있는 경우 아래와 같이 앱이 노출되지 않는다면 Firebase IOS 세팅에 들어가서 team ID랑 appstore ID를 추가하였는지를 확인하면 된다.

앱 설치 여부에 따른 사용자 행동에 대해서 선택할 수 있다. 앱의 App Store 페이지로 넣게 되면, 앱이 설치되지 않은 경우 우리의 앱 스토어로 랜딩을 보내준다.

Andorid 부분도 앱을 선택해 주고, 아래와 같이 설정을 해주면 된다.


이제 마지막으로 선택 옵션을 추가해도 되는데, 우선은 추가하지 않고 만들어보자.

정상적으로 생성이 되었다. 해당 링크에 대한 여러 이벤트 값들을 볼 수 있게 해준다.

다이나믹 링크를 추적하고 싶다면 대시보드에서 생성한 다이나믹 링크를 공유하면 된다. 대시보드를 통한 다이나믹 링크는 개발자가 사용하기 보다는 마케터들이 주로 사용한다.

Flutter

Flutter에서 다이나믹 링크를 생성하는 코드이다. 아래 코드는 shortLink를 생성하는 방식이고, longLink를 생성하고 싶다면 buildShortLink 부분을 buildLink로 변경해주면 된다.

uriPrefix 부분이 우리가 생성한 도메인이 들어가는 부분이고, link는 해당 다이나믹 링크를 클릭하여 앱에 진입시 앱에서 수신하는 딥링크이다.

minimumVersion으로 앱의 최소 버전을 제한하여 다이나믹 링크를 작동시킬 수도 있다. 추가로 IOSParameters의 최소 버전은 사용하지 않더라도 "0" 이렇게 값을 넣어야 정상적으로 작동이 된다.

이 외에도 추가적으로 googleAnalyticsParameters 값을 통해서 GA 캠페인 등록이 가능하고, SocialMetaTagParameters 객체를 생성해서 썸네일 이미지 등을 넣어 공유시 썸네일 이미지가 노출되는 공유하기 기능에 사용할 수도 있다.

만약에 앱 삭제하고 링크 오픈시 스토어로 랜딩이 되지 않는다면, iosParameters에 스토어 ID를 추가해주면 된다.

Future<void> createDynamicLink(BuildContext context, String content) async {
    DynamicLinkParameters _parms = DynamicLinkParameters(
      uriPrefix: "https://tyger.page.link",
      link: Uri.parse("https://tyger.page.link/firebase/dynamicLinks/$content"),
      androidParameters: const AndroidParameters(
        packageName: {your_package_name},
        minimumVersion: 0,
      ),
      iosParameters: const IOSParameters(
        bundleId: {your_bundle_id},
        minimumVersion: '0',
      ),
    );
    ShortDynamicLink _dynamicLink =
        await FirebaseDynamicLinks.instance.buildShortLink(_parms);
    String _shortLink = _dynamicLink.shortUrl.toString();
  }

REST API

DynamicLinks 대시보드를 통해서 숏 링크를 생성해보고, firebase_dynamic_links SDK를 통해서도 링크를 생성해봤다.

SDK를 추가하지 않고 링크 생성을 원한다면 REST API 방식으로도 생성할 수 있다. REST API 방식으로 생성을할 때에는 API Key가 필요한데, 해당 키는 아래의 경로에 들어가면 있다.

dependencies
dependencies:
	http: ^0.13.5
Code

코드만 공유하도록 하겠다.

Future<void> createDynamicLinkWithAPI(BuildContext context, String content) async {
    HapticFeedback.mediumImpact();
    try {
      String _key = {_your_key};
      http.Response _response = await http.post(
        Uri.parse("https://firebasedynamiclinks.googleapis.com/v1/shortLinkskey=$_key"),
        headers: {
          "Content-Type": "application/json",
        },
        body: json.encode({
          "suffix": {
            "option": "SHORT",
          },
          "dynamicLinkInfo": {
            "domainUriPrefix": "https://tyger.page.link",
            "link": "https://tyger.page.link/firebase/dynamicLinks/$content",
            "androidInfo": {
              "androidPackageName": {your_package_name},
            },
            "iosInfo": {
              "iosBundleId": {your_bundle_id},
              "iosAppStoreId": {your_store_id},
            },
            "socialMetaTagInfo": {
            "socialTitle": "$title",
            "socialImageLink": "$imageUrl",
             },
          },
        }),
      );
      if (_response.statusCode == 200) {
        Map<String, dynamic> _result = json
            .decode(utf8.decode(_response.bodyBytes)) as Map<String, dynamic>;
        Clipboard.setData(ClipboardData(text: _result["shortLink"]));
        ScaffoldMessenger.of(context).showSnackBar(SnackBar(
            content: Text(
          _result["shortLink"],
          style:
              const TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
        )));
      }
    } on HttpException catch (error) {
      logger.e(error);
    }
  }

Listener

Listener를 구현하고 라우팅 로직을 개발해보자.

딥링크 수신 개발을 하기 전에 미리 아셔야 하는 부분이 있다. 바로 플랫폼(Android / IOS)별로 다르게 처리를 하여야 한다는 것이다. 공식 문서에는 이러한 문제에 대해서는 언급이 없지만 구글에 검색만 해봐도 리스너 처리 부분의 다양한 이슈가 등록되 있다는 것을 확인할 수 있다.

대체적으로 Android에서는 별다른 이슈 없이 공식 문서를 보고 해도 문제 없이 작동을 하지만, 문제는 IOS 이다. IOS에서는 앱 종료 상태에서 리스너가 작동을 하지 않는다. 하지만 너무 걱정하지 않아도 된다. 제가 여러 방법을 이미 사용하면서 경험했던 해결 방법을 공유할 예정이다.
IOS에서는 이상하게 프로젝트마다도 리스너 부분을 다 다르게 개발을 했었다.. 어떤 써드파티를 사용하고 SDK를 사용하는지와 Info.plist파일 구성에 따라서 다르게 적용이 되어야 한다.

Flutter에서 딥링크를 수신하는 부분에 있어 어떤 이슈가 있는지 확인했으니, 리스너에 대해서도 살펴보도록 하자.

Flutter에서 딥링크를 수신하는 방법은 다양한 방법들이 있지만, 우리는 firebase_dynamic_links 라이브러리를 사용하고 있으니, 해당 라이브러리를 통해서 딥링크를 수신받을 것이다.

수신됬을 때에 어떤 값을 리턴 받을 수 있는지 확인해 보자.

firebase_dynamic_links 라이브러리의 리턴 객체는 PendingDynamicLinkData 객체로 아래의 형태로 리턴을 해준다.

PendingDynamicLinkData({ios: null, android: {clickTimestamp: 1678251850172, minimumVersion: 0}, link: https://tyger.page.link/firebase/dynamicLinks/nomal, utmParameters: {}})

제가 테스트한 다이나믹 링크의 link 파라미터는 아래의 값이다. 위에서 link 값과 동일한 것을 확인할 수 있다.

https://tyger.page.link/firebase/dynamicLinks/nomal

리턴 값의 link 값만 사용하여 라우팅 처리를 해주면 되는 것이다. 여기서 라우터에 관련한 내용까지 다루기에는 내용이 너무 길어져서 라우터에 관한 내용에 대해서는 다루지 않을 예정이며, 단순히 네임드 라우터를 사용하여 받아온 딥링크로 라우팅을 진행할 예정이다.

이제 앱의 상태에 따라 다이나믹 링크를 어떻게 수신받고 처리해야 하는지 코드를 통해서 배워보자.

Router

여기서 라우팅 처리에 사용되는 코드이다. Uri 타입을 받아와 도메인 부분을 제거해준 딥링크를 네임드 파라미터에 넣어주는 걸로 개발하였다.

  void _onRouter(BuildContext context, Uri? path) {
    if (path != null) {
      String _deeplink =
          path.toString().replaceAll("https://tyger.page.link/", "");
      Navigator.of(context).pushNamed(_deeplink);
    }
  }

Listen (Foreground / Background)

앱 실행 중 또는 앱 백그라운드 상태에서 딥링크를 작동시켰을 때에 리스너가 호출되는 부분이다. 코드는 간단하지만 onLink를 listen하기 때문에 한 번만 호출되지 않고 2~3번까지 호출되는 경우가 있다.
onLink.listen은 Stream으로 중복으로 라우트를 처리하지 않도록 로직 구현이 필요하다. 보통 라우터에서 같은 경로를 띄울 수 없게 제한하는 경우도 있고, 라우터를 중복을 제거하고 한 번만 호출하는 방식을 사용하여도 된다.

Foreground / Background 환경에서는 Android, IOS 둘다 정상적으로 잘 실행이 되고있다.

  void _backgroundDynamicLink(BuildContext context) {
    FirebaseDynamicLinks.instance.onLink.listen((event) {
      _onRouter(context, event.link);
    });
  }`

Listen (Terminate)

이제 마지막으로 개발하면서 가장 어려웠던 부분인 Terminated 상태일 때, 딥링크를 수신 받는 방법에 대해서 살펴보겠다.

Terminate 상태는 앱이 실행되지 않았을 때의 상태로 앱이 종료되어 있는 상태이다.

위에서 설명한대로 Android / IOS의 수신 방법이 다르다. IOS의 경우 여러 방법을 공유할 것인데, 수신이 가능한 코드로 개발하면 된다.

먼저 Dynamic Links 공식 문서에 나온 방법 처럼만 해도 되는 Android 먼저 살펴보겠다.

공식문서를 보면 main 함수에서 Firebase 초기화 이 후에 다이나믹 링크 리스너를 수신하라고 되어 있는데, main이 아니어도 첫 스크린 부분에서 실행해줘도 된다.

여기서 리턴되는 객체의 path만 사용하여 라우팅 로직을 개발할 수 있다. main 함수에서 처리를 하면 좋긴 하지만 저는 main 함수에서 처리하지 않고 첫 스크린에서 처리를 할 것이다. main 함수에서 시작하게 되면 Launch 스크린 이 후에 바로 라우터가 작동을 하여서 스플래시 스크린 -> 로그인 처리 -> 메인 스크린 데이터 세팅 이 후에 처리할 수 있도록 보통 개발을 해왔다.

void main() async{
	await Firebase.initializeApp(
    	options: DefaultFirebaseOptions.currentPlatform,
  	);
PendingDynamicLinkData? _data =
        await FirebaseDynamicLinks.instance.getInitialLink();
	if (_data != null) {
     	logger.e(_data.link.path);
    }
}

싱글통 패턴 또는 상태 관리 로직에서 등록해 놓고 첫 스크린에서 실행을 해주면 리스너가 정상적으로 작동을 한다.

여기서 문제가 발생했다. IOS에서는 라우터도 작동되지 않고 딥링크 값을 노출해봐도 null 값으로만 들어온다. 반대로 Android는 라우터는 작동하지 않는데, 딥링크 값은 노출되는 것으로 확인되었다.

Android는 딥링크를 수신은 하지만 라우터가 작동을 하지 않고있는 것인데, 그 이유는 context때문이다.
비슷한 상황이 발생하면 context를 어느 빌드의 context를 사용하는지 확인해서 처리해주면 정상적으로 작동을 한다.

  Future<void> initialDynamicLink(BuildContext context) async {
    PendingDynamicLinkData? _data =
        await FirebaseDynamicLinks.instance.getInitialLink();
    if (_data != null) {
      _onRouter(context, _data.link.path);
    }
  }

자 이제 문제의 IOS를 처리해보자. 만일 위에 방법으로 수신이 됬다면 getInitialLink를 통해서 처리를 하면된다. 저는 이번 블로그 글을 작성하면서 다이나믹 링크를 새로 구현하였는데, Foreground/Background 환경의 리스너에서 수신이 되는 것을 확인하였다.

먼저 Foreground/Background 환경에서 딥링크를 수신하고 있는 부분에 리스너가 작동하는지 확인을 해보시고 안된다면 아래의 방법을 시도해보자.

구글에 IOS 다이나믹 링크 리스너를 검색해보면 가장 많이 나오는 해결 방법이다. 바로 uni_links 라이브러리를 사용하는 것이다.

uni_links는 기존 url_scheme 체계에서 주로 사용되는 방법으로 AppLinks / Universal Links를 처리하도록 도와주는 Flutter 라이브러리이다.

dependencies:
	uni_links: ^0.5.1`

이렇게 사용하면 딥링크를 수신받을 수 있다. 정상적으로 작동되는지 확인해 보자.

String? _uniLinks = await getInitialLink();

딥링크를 수신은 되는데, IOS는 숏링크만 수신이 되고 딥링크는 수신이 되지 않을 경우 아래 코드를 추가하여 딥링크를 얻을 수 있다.

String? _uniLinks = await getInitialLink();
if(_uniLinks != null){
	String _deeplink =  FirebaseDynamicLinks.instance.getDynamicLink(_uniLinks);
}

추가로 firebase_dynamic_links 라이브러리를 사용하지 않고 uni_links 라이브러리를 사용하고자 한다면 Foreground / Background 리스너를 아래와 같은 형태로 사용하면 된다.

 linkStream.listen((String? link) {
      logger.e(link);
    });

마지막으로 네이버/카카오 로그인 또는 어트리뷰션을 사용하고 있어서 아래의 swift 코드를 사용하고 있다면 한 가지 추가적으로 처리를 해주어야 합니다.

   override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
     ...
      }

예를 들어서 네이버 로그인을 추가하면 공식문서에 아래 코드를 swift 파일에 추가하라고 되어있다.

   override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
           NaverThirdPartyLoginConnection.getSharedInstance().application(app, open: url, options: options)
      }

이게 문제인게 네이티브에서 앱 실행시 open url로 딥링크가 전달되기에 Flutter 팀에서는 사용을 하지 말라고 하는 부분인데, 네이버도 그렇고 어트리뷰션 툴들도 그렇고 네이티브에서 url처리를 하게끔한다..

이럴 경우 아래와 같이 처리해주면 된다.

  override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
          if url.absoluteString.contains("thirdPartyLoginResult"){
            NaverThirdPartyLoginConnection.getSharedInstance().application(app, open: url, options: options)
            return true
          } else {
            super.application(app, open:url, options: options)
            return true
          }
      }

저는 위에 공유한 두 가지 방법 중 하나로 사용하고 있고, 지금까지 AppLinks, Universal Links, Url scheme 체계를 4개의 프로젝트에 사용해 왔는데, 두 가지 방법 중 하나에서는 작동이 되었었다.

혹시라도 위의 방법으로도 작동이 되지 않는다면 app_links 라이브러리를 시도해보시고, 아니면 swift 코드를 통해서 네이티브에서 직접 수신받아 와도 된다. 사실 네이티브에서 수신받아 처리하는 방법이 제일 깔끔하고 확실하게 작동하기는 한다.

하지만 Flutter에서 처리가 가능하다면 아무래도 Flutter를 통해서 개발을 해보시길 권장한다.

스토어로 강제 이동하는 방법

다이나믹 링크의 기능 중 하나인 스토어 강제 랜딩에 대해서 잠시 다뤄보겠다. 만약에 이미 배포되있는 앱이 다이나믹 링크를 공유받으면 어떤 이슈가 발생하겠는가 ? IOS에서는 문제가 없다. 이전에 배포되있는 앱에는 assoicated domains가 등록되어 있지 않아 무조건 스토어로만 보내진다. 하지만 안드로이드는 다른 이슈가 발생할 수 있다.
우리가 manifest.xml 파일에 등록한 스킴을 기존에도 사용하고 있었다면 무조건 앱이 열리게 된다. 하지만 기존 배포된 앱에서는 SDK가 내장되어 있지 않아 딥링크를 구문분석 할 수도 없다.

또는 특정 버전에서 공유한 링크에 대해서 이하 버전을 사용하는 사용자는 진입을 시키지 않고 스토어로 보내지게 처리를 하고 싶을 수도있다. 이럴 때 사용할 수 있는 기능이 최소 버전이다.

위에서 다이나믹 링크를 생성할 때에 최소 버전을 넣을 수 있게 되있는데, 여기서 넣어지는 최소 버전은 우리가 생각하는 버전이랑은 조금 다르다.

앱을 배포할 때에 버전을 어디서 변경하였나 ? pubspec.yaml 파일에서 수정을 했을 것이다. 하지만 IOS는 직접 Xcode를 들어가서 버전을 수정했어야 했다.

안드로이드의 경우 버전 정보를 pubspec.yaml의 버전을 가져오고 있어서 따로 수정하지 않아도 자동으로 변경이 되었던 것이다.

project > android > app > build.gradle

사진에서 봤듯이 versionCode와 versionName이 따로 나눠져있는데, versionName이 play store에 노출되는 version이다.

그렇다면 다이나믹링크 생성시 사용하는 버전은 어떤 것일까 ? 바로 versionCode를 사용하고 있는 것이다.

1.10.1+98 이렇게 버전이 있다고 가정을 해보자. 여기서 versionName에 해당되는 부분이 1.10.1이고, versionCode가 바로 98인 것이다.

이제 방법은 알았으니, 다이나믹 링크 생성시 코드를 수정해서 테스트를 해보자. IOS에서는 작동하지 않는다.

Firebase

androidParameters: const AndroidParameters(
        packageName: {your_package_name},
        minimumVersion: 99,
      ),

REST API

"androidInfo": {
      "androidPackageName": {your_package_name},
      "androidMinPackageVersionCode": 99,
    },

마무리

다이나믹 링크에 대해서 이해가 되지 않거나 안되는 부분이 있다면 댓글 남겨주세요. 몇 일에 걸쳐서 작성된 글이다 보니 내용이 중간에 추가된 부분도 있고 다양한 방법을 최대한 공유하기 위해 정리되지 않고 작성을 하였다..ㅠㅠ

다이나믹 링크는 여러 번 개발을 해봤지만, 할 때마다 개발 방법이 계속 달라지는 느낌이 있다. 실제로도 다르게 개발을 하기도 했다. 다이나믹 링크는 개발 난이도가 쉬운편은 아니라 좌절하지 마시고 여러번 시도해 보세요. 방법은 반드시 있습니다 !

여기서는 다루지 않았지만 다이나믹 링크를 통해서 앱이 오픈되고 딥링크를 수신하여 처리하는 부분에 대해서는 라우터를 등록하고 사용하여야 하기에 여기서는 설명을 하지 못한 부분이 조금 아쉽다.

실제 앱 개발 프로젝트를 블로그에 공유할 예정인데, 그 때 다이나믹 링크와 관련된 부분도 포함하여 프로덕션 앱에서 딥링크를 어떻게 수신받아 처리하고 하는지에 대해서도 포함하여 프로젝트를 진행해 보겠다.

profile
Flutter Developer

3개의 댓글

comment-user-thumbnail
2023년 8월 7일

향후 동적링크를 더 이상 지원 안한다고 하는데, 동적 링크를 사용한 부분은 어떻게 변경해야 할지 궁금합니다.

1개의 답글