Flutter 앱에서 Deep Link 구현하며 겪은 삽질
배경
마케팅팀 요청으로 특정 상품 페이지로 직접 이동하는 링크가 필요했다. 웹은 이미 구현되어 있었고, Flutter 앱에도 동일한 기능을 추가하는 작업이었다.
Android 설정
AndroidManifest.xml에 intent-filter를 추가했다.
<intent-filter android:autoVerify="true">
<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="example.com"
android:pathPrefix="/products" />
</intent-filter>
Android 12부터는 android:autoVerify를 제대로 처리하려면 서버에 .well-known/assetlinks.json 파일이 필요했다. 이 부분을 몰라서 한참 헤맸다.
iOS 설정
Info.plist에 URL Scheme 추가했다.
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
Universal Link를 위해서는 Signing & Capabilities에서 Associated Domains를 추가하고, 서버에 apple-app-site-association 파일도 배포해야 했다.
Flutter 코드
uni_links 패키지를 사용했다.
import 'package:uni_links/uni_links.dart';
StreamSubscription? _sub;
void initDeepLinks() {
_sub = linkStream.listen((String? link) {
if (link != null) {
handleDeepLink(link);
}
});
}
void handleDeepLink(String link) {
final uri = Uri.parse(link);
if (uri.pathSegments.contains('products')) {
final productId = uri.pathSegments.last;
Navigator.pushNamed(context, '/product/$productId');
}
}
앱이 종료된 상태에서 링크를 눌렀을 때도 처리하려면 getInitialLink()도 체크해야 했다.
겪은 문제
- Android에서 앱이 이미 실행 중일 때 새 인스턴스가 생성되는 문제 -
launchMode를singleTask로 변경해서 해결 - iOS 시뮬레이터에서 Universal Link가 작동하지 않음 - 실제 기기에서만 테스트 가능했다
- 서버 파일 검증이 안 되는 경우 - HTTPS 필수, MIME 타입 확인 필요
결과
결국 양쪽 플랫폼에서 정상 작동했다. 하지만 각 플랫폼의 설정 방식이 달라 문서를 여러 번 읽어야 했고, 특히 검증 파일 배포는 백엔드 팀과 협업이 필요했다.
앱 링크는 한 번 제대로 설정해두면 마케팅 활용도가 높아서 투자할 만한 작업이었다.