Flutter에서 딥 링크(Deep Link)를 어떻게 구현하나요?
질문
Flutter 앱에서 딥 링크를 구현하는 방법을 설명해주세요.
답변
Flutter에서 딥 링크(Deep Link)는 앱의 특정 콘텐츠로 직접 이동할 수 있는 URL을 의미합니다. 이는 웹 URL, QR 코드, 푸시 알림 등에서 앱의 특정 화면으로 바로 이동하는 기능을 가능하게 합니다. Flutter에서는 여러 방법으로 딥 링크를 구현할 수 있습니다.
1. 기본 딥 링크 구현 과정
딥 링크를 구현하기 위한 기본적인 단계는 다음과 같습니다:
1.1 플랫폼별 설정
Android 설정 (AndroidManifest.xml)
<manifest ...>
<application ...>
<activity ...>
<!-- 딥 링크 인텐트 필터 추가 -->
<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="open" />
</intent-filter>
<!-- HTTP/HTTPS URL 처리를 위한 인텐트 필터 -->
<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.example.com" />
</intent-filter>
</activity>
</application>
</manifest>
iOS 설정 (Info.plist)
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.example.myapp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
<!-- Universal Links를 위한 설정 -->
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:myapp.example.com</string>
</array>
iOS에서 Universal Links를 위해서는 추가로 apple-app-site-association
파일을 웹서버에 호스팅해야 합니다.
1.2 Flutter에서 딥 링크 처리
방법 1: uni_links 패키지 사용
가장 쉬운 방법은 uni_links
패키지를 사용하는 것입니다:
// pubspec.yaml
dependencies:
uni_links: ^0.5.1
// 코드 구현
import 'package:uni_links/uni_links.dart';
import 'dart:async';
class _MyAppState extends State<MyApp> {
StreamSubscription? _deepLinkSubscription;
@override
void initState() {
super.initState();
initDeepLinks();
}
Future<void> initDeepLinks() async {
// 앱이 이미 실행 중일 때 딥 링크 처리
_deepLinkSubscription = linkStream.listen((String? link) {
if (link != null) {
handleDeepLink(link);
}
}, onError: (err) {
print('딥 링크 오류: $err');
});
// 앱이 종료된 상태에서 딥 링크로 시작된 경우 처리
try {
final initialLink = await getInitialLink();
if (initialLink != null) {
handleDeepLink(initialLink);
}
} catch (e) {
print('초기 딥 링크 오류: $e');
}
}
void handleDeepLink(String link) {
print('딥 링크 수신: $link');
// URL 파싱 및 처리 로직
Uri uri = Uri.parse(link);
// 예: myapp://open/product/123 처리
if (uri.host == 'open') {
final pathSegments = uri.pathSegments;
if (pathSegments.length >= 2 && pathSegments[0] == 'product') {
final productId = pathSegments[1];
navigateToProductDetails(productId);
}
}
}
void navigateToProductDetails(String productId) {
// 해당 제품 상세 페이지로 네비게이션
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ProductDetailScreen(productId: productId),
),
);
}
@override
void dispose() {
_deepLinkSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
// ...앱 구성
);
}
}
2. 고급 라우팅을 통한 딥 링크 구현
더 복잡한 앱에서는 라우팅 시스템과 딥 링크를 통합하는 것이 좋습니다:
2.1 go_router 패키지 사용
go_router
는 URL 기반 라우팅을 제공하여 딥 링크 처리에 적합합니다:
// pubspec.yaml
dependencies:
go_router: ^10.1.2
// 코드 구현
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
final GoRouter _router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomeScreen(),
),
GoRoute(
path: '/product/:id',
builder: (context, state) {
final productId = state.pathParameters['id']!;
return ProductDetailScreen(productId: productId);
},
),
GoRoute(
path: '/category/:name',
builder: (context, state) {
final categoryName = state.pathParameters['name']!;
return CategoryScreen(category: categoryName);
},
),
],
redirect: (context, state) {
// 필요한 경우 리다이렉션 로직 추가
return null; // 리다이렉션 없음
},
);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
title: '딥 링크 데모',
);
}
}
이제 myapp://open/product/123
또는 https://myapp.example.com/product/123
과 같은 딥 링크는 자동으로 상품 상세 페이지로 라우팅됩니다.
3. 파이어베이스 다이나믹 링크 사용하기
Firebase Dynamic Links는 앱 설치 여부에 관계없이 사용자를 올바른 위치로 안내하는 강력한 딥 링크 솔루션입니다:
// pubspec.yaml
dependencies:
firebase_core: ^2.15.1
firebase_dynamic_links: ^5.3.5
// 코드 구현
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_dynamic_links/firebase_dynamic_links.dart';
Future<void> initDynamicLinks() async {
// 앱이 종료된 상태에서 다이나믹 링크로 시작된 경우
final PendingDynamicLinkData? initialLink = await FirebaseDynamicLinks.instance.getInitialLink();
if (initialLink != null) {
final Uri deepLink = initialLink.link;
handleDynamicLink(deepLink);
}
// 앱이 포그라운드/백그라운드 상태일 때 다이나믹 링크 처리
FirebaseDynamicLinks.instance.onLink.listen(
(dynamicLinkData) {
final Uri deepLink = dynamicLinkData.link;
handleDynamicLink(deepLink);
},
onError: (error) {
print('다이나믹 링크 오류: $error');
}
);
}
void handleDynamicLink(Uri deepLink) {
// URI 파라미터에서 필요한 정보 추출
final pathSegments = deepLink.pathSegments;
final queryParams = deepLink.queryParameters;
if (pathSegments.isNotEmpty) {
if (pathSegments[0] == 'product' && pathSegments.length > 1) {
final productId = pathSegments[1];
// 상품 상세 페이지로 이동
navigateToProductDetail(productId);
} else if (pathSegments[0] == 'promotion' && queryParams.containsKey('code')) {
final promoCode = queryParams['code'];
// 프로모션 페이지로 이동
navigateToPromotion(promoCode!);
}
}
}
3.1 다이나믹 링크 생성하기
Future<Uri> createDynamicLink(String productId) async {
final DynamicLinkParameters parameters = DynamicLinkParameters(
uriPrefix: 'https://myapp.page.link',
link: Uri.parse('https://myapp.example.com/product/$productId'),
androidParameters: AndroidParameters(
packageName: 'com.example.myapp',
minimumVersion: 1,
),
iosParameters: IOSParameters(
bundleId: 'com.example.myapp',
minimumVersion: '1.0.0',
appStoreId: '123456789',
),
socialMetaTagParameters: SocialMetaTagParameters(
title: '제품 상세 보기',
description: '멋진 제품을 확인해보세요!',
imageUrl: Uri.parse('https://example.com/images/product.jpg'),
),
);
final shortLink = await FirebaseDynamicLinks.instance.buildShortLink(parameters);
return shortLink.shortUrl;
}
4. 딥 링크 테스트하기
4.1 adb 명령어로 테스트 (Android)
# 커스텀 스킴 테스트
adb shell am start -a android.intent.action.VIEW -d "myapp://open/product/123" com.example.myapp
# HTTP 링크 테스트
adb shell am start -a android.intent.action.VIEW -d "https://myapp.example.com/product/123" com.example.myapp
4.2 iOS 시뮬레이터에서 테스트
- 시뮬레이터 실행
- Safari 열기
- URL 입력:
myapp://open/product/123
5. 앱 링크(App Links) 및 유니버설 링크(Universal Links) 구현
보안이 강화된 딥 링크 구현을 위해 Android의 App Links와 iOS의 Universal Links를 설정할 수 있습니다.
5.1 Android App Links
assetlinks.json
파일을 웹사이트의 .well-known
디렉토리에 호스팅해야 합니다:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.myapp",
"sha256_cert_fingerprints": ["SHA256 인증서 지문"]
}
}
]
5.2 iOS Universal Links
apple-app-site-association
파일을 웹사이트의 .well-known
디렉토리에 호스팅해야 합니다:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "팀ID.com.example.myapp",
"paths": ["*"]
}
]
}
}
6. 딥 링크 처리 시 고려사항
사용자 경험: 딥 링크로 들어온 사용자가 백 버튼을 눌렀을 때 적절한 화면으로 이동하도록 네비게이션 스택을 관리해야 합니다.
인증 처리: 로그인이 필요한 화면으로 딥 링크를 통해 들어온 경우, 로그인 상태를 확인하고 필요시 로그인 화면으로 리다이렉션해야 합니다.
오류 처리: 잘못된 형식의 딥 링크나 존재하지 않는 콘텐츠에 대한 요청을 적절히 처리해야 합니다.
딥 링크 분석: 딥 링크를 통한 유입 경로를 분석하여 마케팅 캠페인의 효과를 측정할 수 있습니다.
앱 설치 전환: 앱이 설치되지 않은 경우 스토어로 안내하는 로직을 구현해야 합니다(Firebase Dynamic Links 사용 시 자동 처리).
요약
Flutter에서 딥 링크를 구현하는 방법은 다음과 같이 요약됩니다:
플랫폼 설정: Android와 iOS 각각의 설정 파일에 딥 링크 스킴과 호스트를 등록합니다.
링크 처리:
uni_links
패키지 또는 라우팅 라이브러리(go_router
등)를 사용하여 링크를 처리합니다.고급 기능: Firebase Dynamic Links를 사용하여 설치 유도 및 복잡한 딥 링크 시나리오를 처리할 수 있습니다.
보안 강화: App Links와 Universal Links를 구현하여 보다 안전하고 신뢰할 수 있는 딥 링크 경험을 제공합니다.
딥 링크는 사용자에게 원활한 앱 경험을 제공하고 마케팅 효과를 높이는 중요한 기능입니다. 적절한 딥 링크 구현은 앱의 사용성과 참여도를 크게 향상시킬 수 있습니다.