[Flutter] Deep Link를 적용해보자! (w/ Go Router)

건전한건전지·2025년 7월 29일
0

Flutter/Dart

목록 보기
7/7
post-thumbnail

들어가며

https://naver.com을 주소창에 입력하면 네이버 사이트로 이동합니다.
앱 환경에서도 비슷한 기능이 있는데 이것을 'Deep Link'(딥링크)라 부릅니다.
딥링크를 사용하여 특정 주소를 입력하면 앱이 실행되거나 앱 내 특정 화면으로 이동할 수 있습니다.

이 포스트에서 Deep Link란 무엇이며 Flutter에서 어떻게 적용하는지 알아보겠습니다.

미리보기

딥링크 URL(details/12?query=aaaaa)을 실행한 결과입니다.


딥링크란?

딥링크란 외부 URL을 통해 앱 내의 지정된 화면·기능·콘텐츠로 직접 이동시키는 기술을 말합니다.
쿠팡 상품 링크를 누르면 쿠팡 앱이 켜지고 상품 상세 화면으로 이동하는 바로 그 기술이죠.

종류

딥링크는 어떤 기준으로 구분하냐에 따라 달라지지만, 여기서는 일반적으로 사용되는 기준으로 정리하였습니다.

Custom URL Schemes(iOS) & Deep Links(Android)

일반적으로 사용되고 쉬운 딥링크 방식입니다.
앱에 Scheme(스킴)을 등록하는 형태로 앱을 구분하고, 특정 스킴을 호출하면 앱이 실행되는 원리입니다.
호스트가 필요없으며 어떤 스킴이라도 허용하여 동작합니다.

유튜브를 실행하고자한다면 youtube://
카카오톡을 실행하고자한다면 kakaotalk://

스킴 뒤에는 path로 특정 페이지를 설정하는데 Scheme://path 형식으로 설정합니다.
예를 들어, 유튜브의 A 채널 홈을 열고자한다면 youtube://channel/a로 URL을 사용합니다.

스킴 방식은 정말 편리하지만 큰 단점이 있는데 스킴은 중복될 수가 있습니다.

'myApp'이라는 스킴은 누구나 사용할 수 있습니다.
또, 내가 사용하려는 스킴을 어떤 앱이 사용하고 있는지 확인 할 수도 없습니다. 고유하다고 생각한 'myApp' 스킴을 추후 다른 개발자가 사용할 수도 있습니다.

스킴이 중복되면 어떤 일이 일어날까요?
스킴이 중복되면 아래와 같이 연결 프로그램을 선택하는 화면이 뜹니다.

결국 애플과 구글에서는 App Links(Android), Universal links(iOS)를 개발하였습니다.

Universal Links(iOS) & App Links(Android)

naver나 youtube는 고유의 도메인 주소가 있습니다. naver.com이나 youtube.com이죠.
그렇기에 도메인 주소는 만료되거나 사라지지 않는 한 소유자를 특정 할 수 있습니다.
두 딥링크는 이러한 특징을 사용합니다.

이러한 방식은 Custom URL Schemes 방식보다 다소 복잡하고, 특정 도메인(naver.com, youtube.com)의 https 스킴에만 동작합니다.
또한, iOS는 apple-app-site-association, Android는 assetlinks.json이라는 특정 파일이 웹서버에 존재해야합니다.
하지만, 이러한 파일을 HTTPS 루트에 배포하여 스푸핑 방지가 가능한 이점이 있습니다.

하지만, 이러한 도메인 방식도 한계가 있습니다.
어떤 상품의 상세 페이지를 공유받아 링크를 눌렀을 때, 앱이 설치되어 있다면 정상적으로 상세 페이지로 이동하지만 앱이 설치되어 있지 않다면 웹으로 이동하게 됩니다.

이러한 한계를 개선하기 위해 Deferred deep link(지연된 딥링크)가 등장합니다.

문자 그대로 지연된 딥링크를 의미합니다.
앱이 설치되어있지 않은 유저에게는 딥링킹을 할 수 없기 때문에, 해당 유저가 링크를 클릭한 후 앱을 설치하고 앱을 실행하기까지 기다렸다가 딥링크를 실행해 주는 방식입니다.

하지만, 위에서 설명한 과정을 이행하기 위해서는 유저가 링크를 클릭했을 때의 정보와 앱을 설치하고 실행한 유저의 정보, 딥링크를 보관하고 내려줄 서버 등이 필요하고 OS 별로 각각의 딥링크가 필요하기때문에 직접 구현은 어려운 편입니다.

이러한 Deferred deep link를 쉽게 제공하는 Dynamic link(firebase), One link(Appsflyer) 등의 솔루션이 있습니다.

Firebase의 Dynamic link는 25년 8월 25일자로 지원 종료되었습니다.

Firebase의 Dynamic link, Appsflyer의 One link는 하나의 Deferred deep link URL로 다양한 OS 환경을 지원합니다.
즉, 하나의 링크로 안드로이드 유저는 플레이 스토어로, 애플 유저는 앱스토어, 웹 유저는 웹 페이지로 이동이 가능합니다.


딥링크 구현

Flutter에서 딥링크를 사용하려면 네이티브 플랫폼 설정과 Dart-Native 간 통신하는 로직이 필요합니다.
통신 로직은 손수 구현하려면 많은 작업이 필요하기에 잘 만들어진 go router 패키지를 사용할 것입니다.

Flutter 설정

Flutter 프로젝트를 만들고 go_router 패키지를 추가합니다.

flutter pub add go_router

main.dart 파일을 수정합니다.

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(MaterialApp.router(routerConfig: router));
}

/// This handles '/' and '/details'.
final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (_, __) => Scaffold(
        appBar: AppBar(title: const Text('Home Screen')),
        body: Center(child: Text('Home')),
      ),
      routes: [
        GoRoute(
          path: 'details/:id',
          builder: (_, state) => Scaffold(
            appBar: AppBar(title: const Text('Details Screen')),
            body: Center(
              child: Text(
                'Details ${state.pathParameters['id']} / ${state.uri.queryParameters['query']}',
              ),
            ),
          ),
        ),
      ],
    ),
  ],
);

details에 path 파라미터와 query 파라미터가 잘 전달되는지 테스트를 위해 추가한 것입니다.

플랫폼별 설정

Android

Android는 Deep Links, App Links 방식 둘 다 AndroidManifest.xml을 수정합니다.

// AndroidManifest.xml

<activity>
  ...
  // Deep Links 설정
  <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" android:host="myHost"/> <-- scheme 등록
  </intent-filter>
  
  // App Links 설정
  <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="myApp.com"/>
  </intent-filter>
</activity>

App Links 방식은 여기에 추가로 웹서버에 파일을 업로드해야합니다.
먼저, assetlinks.json 파일을 작성합니다.

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.deeplink_cookbook",
    "sha256_cert_fingerprints":
    ["FF:2A:CF:7B:DD:CC:F1:03:3E:E8:B2:27:7C:A2:E3:3C:DE:13:DB:AC:8E:EB:3A:B9:72:A1:0E:26:8A:F5:EC:AF"]
  }
}]

package_name에는 프로젝트의 Android application ID를 입력하고 sha256 지문을 입력합니다.
만든 파일을 웹서버의 <domain>/.well-known/assetlinks.json 경로에 업로드합니다.

iOS

iOS는 Custom URL SchemeUniversal Links 방식의 설정이 다릅니다.

Custom URL Scheme

XCode를 통해 URL Types 아이템을 추가하거나

info.plist 파일의 dict 아래에 다음을 추가합니다.
{scheme}은 원하는 이름으로 변경합니다.

<array>
	<dict>
		<key>CFBundleTypeRole</key>
		<string>Editor</string>
		<key>CFBundleURLSchemes</key>
		<array>
			<string>{scheme}</string>
		</array>
	</dict>
</array>

XCode의 Signing & Capabilities 에서 Associated Domains 에서 등록합니다.
Domainsapplinks:<host_domain>을 추가합니다.
예를 들어, 도메인이 naver.com이라면 applinks:naver.com이 됩니다.

개인 계정일 경우에는 XCode에서 Associated Domains 설정을 할 수 없으니 다른 방법을 써야합니다.

먼저, ios/Runner/Runner.entitlements파일을 수정합니다.

<plist version="1.0">
<dict>
  ...
  // 추가
  <key>com.apple.developer.associated-domains</key>
  <array>
    <string>applinks:example.com</string>
  </array>
</dict>
</plist>

추가 후 Xcode를 실행하면 Signing & Capabilities 탭에 Associated Domains 항목이 추가된 것을 볼 수 있습니다
위와 같이 Domains에 아이템을 추가합니다.

Android와 마찬가지로 웹서버에 특정 파일을 업로드해야합니다.

apple-app-site-association 파일을 작성합니다.

JSON 형태지만 파일 확장자가 없어야합니다.

{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appIDs": [
          "ABCD1234.com.example"
        ],
        "paths": [
          "*"
        ],
        "components": [
          {
            "/": "/*"
          }
        ]
      }
    ]
  },
  "webcredentials": {
    "apps": [
      "ABCD1234.com.example"
    ]
  }
}

appID<team id>.<bundle id>형태힙니다.
예를 들어, team id가 ABCD1234이고 bundle id가 com.example이라면 ABCD1234.com.example이 됩니다.

만든 파일을 웹서버의 <domain>/.well-known/apple-app-site-association 경로에 업로드합니다.

딥링크 테스트

Android

에뮬레이터나 실기기를 연결하고 앱을 빌드합니다.

정상적으로 빌드가 완료되면 아래 커맨드를 터미널에 입력합니다.
{web-domain}{package_name}은 설정한 값으로 변경합니다.

adb shell 'am start -a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d "https://{web-domain}/details/12?query=aaaaa"' \
{package_name}

iOS

시뮬레이터나 실기기를 연결하고 앱을 빌드합니다.

시뮬레이터

정상적으로 빌드가 완료되면 아래 커맨드를 터미널에 입력합니다.
{web-domain}은 설정한 값으로 변경합니다.

 xcrun simctl openurl booted https://{web-domain}/details/12?query=hello

실기기

노트앱을 실행하고
https://{web-domain}/details/12?query=hello을 입력하고 링크를 실행합니다.

마치며

딥링크는 단순히 앱을 여는 기술이 아니라, 사용자를 원하는 지점으로 바로 안내해 주는 경험 설계 도구입니다.
마케팅 캠페인, 푸시 알림 등 다양한 채널과 앱을 자연스럽게 연결해 전환율을 높이고, 사용자 이탈을 줄이기에 개발자, 기획자, 경영자 모두에게 중요하다고 생각합니다.

사용자 중심 경험을 고민한다면, 딥링크는 선택이 아니라 필수라 생각합니다.

참조

https://docs.flutter.dev/ui/navigation/deep-linking
https://pub.dev/packages/app_links
https://medium.com/flutter-community/deep-links-and-flutter-applications-how-to-handle-them-properly-8c9865af9283

profile
Flutter 공부를 위한 블로그입니다.

0개의 댓글