
앱이 안 열려요 (딥링크, 유니버설 링크 완전 정복)
카카오톡이나 문자의 링크를 눌렀는데 앱이 안 켜지고 웹페이지만 뜹니다. Android App Links, iOS Universal Links 설정, 그리고 `go_router` 처리까지 딥링크의 모든 것을 파헤칩니다.

카카오톡이나 문자의 링크를 눌렀는데 앱이 안 켜지고 웹페이지만 뜹니다. Android App Links, iOS Universal Links 설정, 그리고 `go_router` 처리까지 딥링크의 모든 것을 파헤칩니다.
로그인 화면을 만들었는데 키보드가 올라오니 노란 줄무늬 에러가 뜹니다. resizeToAvoidBottomInset부터 스크롤 뷰, 그리고 채팅 앱을 위한 reverse 팁까지, 키보드 대응의 모든 것을 정리해봤습니다.

안드로이드는 오는데 iOS는 조용합니다. 혹은 앱이 켜져 있을 때만 옵니다. Background/Terminated 상태 처리, APNs 인증서, 그리고 Notification Channel 설정까지 완벽하게 해결합니다.

안드로이드는 Xcode보다 낫다고요? Gradle 지옥에 빠져보면 그 말이 쏙 들어갈 겁니다. minSdkVersion 충돌, Multidex 에러, Namespace 변경(Gradle 8.0), JDK 버전 문제, 그리고 의존성 트리 분석까지 완벽하게 해결해 봅니다.

Debug에선 잘 되는데 Release에서만 죽나요? 범인은 '난독화'입니다. R8의 원리, Mapping 파일 분석, 그리고 Reflection을 사용하는 라이브러리를 지켜내는 방법(@Keep)을 정리해봤습니다.

마케팅 팀에서 이벤트를 한다고 문자를 돌렸습니다.
https://myapp.com/event/123
이 링크를 누르면 멋지게 우리 앱의 이벤트 페이지로 이동해야 합니다.
그런데 현실은? 그냥 사파리(웹브라우저)가 열리고 맙니다. 심지어 앱이 설치되어 있는데도요. 사용자는 "뭐야, 앱 있는데 왜 로그인을 또 새로 하래?"라며 이탈합니다.
옛날에는 myapp://event/123 같은 Custom Scheme을 썼습니다.
하지만 이건 보안 문제(중복 가능)가 있어서, 요즘은 표준인 App Link (안드로이드) / Universal Link (iOS)를 써야 합니다.
핵심 원리는 "웹사이트가 앱을 보증한다"입니다. 앱만 설정해서는 안 되고, 반드시 도메인(서버)에도 "이 앱이 내꺼 맞다"라는 증명 파일을 올려둬야 합니다.
이걸 안 하면 아무리 앱을 고쳐도 동작 안 합니다.
assetlinks.json)도메인의 /.well-known/assetlinks.json 경로에 파일이 있어야 합니다.
SHA-256 지문이 앱 서명 키와 정확히 일치해야 합니다. (Play Console 서명 키 주의!)
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.myapp",
"sha256_cert_fingerprints": ["YOUR_SHA256_HASH"]
}
}]
apple-app-site-association)도메인의 /.well-known/apple-app-site-association (확장자 없음!) 경로에 있어야 합니다.
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAMID.com.example.myapp",
"paths": [ "/event/*", "/user/*" ]
}
]
}
}
주의: 이 파일들은 반드시 Content-Type: application/json으로 서빙되어야 하며, 리다이렉트 없이 200 OK가 떠야 합니다.
이제 앱에게 "내가 저 도메인 주인이야"라고 알려줘야 합니다.
AndroidManifest.xml)autoVerify="true"가 핵심입니다. 이게 있어야 사용자에게 "이 앱으로 열래?"라고 묻지 않고 바로 앱을 엽니다.
<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="myapp.com" />
</intent-filter>
Signing & Capabilities -> Associated Domains 추가.
applinks:myapp.com 입력.
(주의: https://를 붙이면 안 됩니다. 그냥 도메인만.)
go_router)앱이 켜지는 것까진 성공했습니다. 이제 해당 페이지로 이동시켜야 합니다.
go_router를 쓰면 아주 쉽습니다.
final goRouter = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomePage(),
routes: [
GoRoute(
path: 'event/:id',
builder: (context, state) {
final id = state.pathParameters['id'];
return EventPage(id: id);
},
),
],
),
],
);
이제 https://myapp.com/event/123으로 들어오면 자동으로 EventPage(id: 123)을 띄워줍니다.
앱이 설치 안 된 사용자가 링크를 누르면 어떻게 될까요? 앱스토어로 이동시킵니다. 사용자가 앱을 설치하고 처음 실행했을 때, 방금 눌렀던 그 링크(event/123)로 보내주고 싶다면?
이걸 Deferred Deep Link라고 합니다. Firebase Dynamic Links가 2025년에 종료되므로, AppsFlyer나 Branch 같은 유료 솔루션을 쓰거나 직접 클립보드 매칭 로직을 구현해야 합니다.
딥링크로 들어왔을 때, 단순히 페이지만 띡 보여주는 게 아니라 Bottom Navigation Bar (Scaffold)가 유지된 상태로 보여야 할 때가 있습니다.
이럴 땐 ShellRoute를 씁니다.
ShellRoute(
builder: (context, state, child) {
return Scaffold(
body: child,
bottomNavigationBar: MyBottomBar(),
);
},
routes: [
GoRoute(path: '/event/:id', ...),
],
)
이렇게 하면 외부에서 링크 타고 들어와도 앱의 네비게이션 구조가 안 깨집니다.
실제로는 Dev, Staging, Prod 환경이 나뉩니다. 딥링크 도메인도 달라야 합니다.
dev.myapp.commyapp.comiOS (XCConfig 사용):
Associated Domains 값에 변수를 사용하세요. applinks:$(DEEP_LINK_DOMAIN).
그리고 Debug.xcconfig, Release.xcconfig에서 값을 다르게 설정합니다.
Android (Manifest Placeholders):
build.gradle에서 manifestPlaceholders를 사용합니다.
// build.gradle
productFlavors {
dev {
manifestPlaceholders = [hostName: "dev.myapp.com"]
}
prod {
manifestPlaceholders = [hostName: "myapp.com"]
}
}
<!-- AndroidManifest.xml -->
<data android:scheme="https" android:host="${hostName}" />
이렇게 해야 개발 중 테스트할 때 실제 운영 서버로 넘어가는 사고를 막을 수 있습니다.
매번 문자 보내서 테스트할 순 없습니다. 터미널 명령어로 시뮬레이션 하세요.
Android:
adb shell am start -W -a android.intent.action.VIEW -d "https://myapp.com/event/123" com.example.myapp
iOS (Simulator):
xcrun simctl openurl booted "https://myapp.com/event/123"
iOS 메모 앱이나 사파리에 링크를 적어두고 클릭해보는 것이 가장 확실합니다. (주소창에 치면 검색으로 넘어가버리는 경우가 많음)
assetlinks.json과 apple-app-site-association 없이는 절대 안 됨.autoVerify: 안드로이드에서 이거 빼먹으면 브라우저/앱 선택창 뜸.Associated Domains: iOS는 applinks: 접두어 필수.