📋 Daftar Isi
1. Apa itu Deep Linking?
Deep linking memungkinkan user membuka konten spesifik di dalam aplikasi mobile melalui URL. Alih-alih membuka halaman utama, user langsung diarahkan ke halaman produk, profil, atau konten tertentu.
Jenis Deep Linking
URL Scheme
myapp://product/123
Sederhana, tidak fallback
Sederhana, tidak fallback
→
Universal Links
https://myapp.com/p/123
iOS, fallback ke web
iOS, fallback ke web
→
App Links
https://myapp.com/p/123
Android, verified
Android, verified
| Metode | Platform | Fallback Web | Verifikasi | Trust Level |
|---|---|---|---|---|
| URL Scheme | Keduanya | Tidak | Tidak | Rendah |
| Universal Links | iOS 9+ | Ya | apple-app-site-association | Tinggi |
| App Links | Android 6+ | Ya | assetlinks.json | Tinggi |
| Intent Filter | Android | Tidak | Tidak | Sedang |
2. Custom URL Schemes
Kotlin — Android URL Scheme
<activity android:name=".MainActivity">
<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="product"
android:pathPrefix="/detail" />
</intent-filter>
</activity>
// Handle di Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleDeepLink(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleDeepLink(intent)
}
private fun handleDeepLink(intent: Intent?) {
val data = intent?.data ?: return
// data = myapp://product/detail?id=123
when (data.host) {
"product" -> {
val productId = data.getQueryParameter("id")
navigateToProduct(productId)
}
"profile" -> {
val userId = data.lastPathSegment
navigateToProfile(userId)
}
}
}
}
Swift — iOS URL Scheme
// Info.plist //CFBundleURLTypes //// // SceneDelegate.swift func scene(_ scene: UIScene, openURLContexts URLContexts: Set// //CFBundleURLSchemes //// myapp CFBundleURLName //com.myapp //) { guard let url = URLContexts.first?.url else { return } handleDeepLink(url) } func handleDeepLink(_ url: URL) { guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return } switch components.host { case "product": let productId = components.queryItems?.first(where: { $0.name == "id" })?.value navigateToProduct(productId) case "profile": let userId = components.path.trimmingCharacters(in: CharacterSet(charactersIn: "/")) navigateToProfile(userId) default: break } }
3. Universal Links (iOS)
JSON — apple-app-site-association
// File: https://myapp.com/.well-known/apple-app-site-association
{
"applinks": {
"apps": [],
"details": [
{
"appIDs": ["TEAMID.com.myapp"],
"components": [
{
"/": "/product/*",
"comment": "Product detail pages"
},
{
"/": "/profile/*",
"comment": "User profile pages"
},
{
"/": "/order/*/track",
"comment": "Order tracking"
},
{
"/": "/share/*",
"comment": "Share links",
"exclude": true
}
]
}
]
}
}
Swift — Universal Links Handler
// AppDelegate.swift
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else {
return false
}
handleUniversalLink(url)
return true
}
func handleUniversalLink(_ url: URL) {
let pathComponents = url.pathComponents.filter { $0 != "/" }
if pathComponents.first == "product",
pathComponents.count > 1 {
let productId = pathComponents[1]
showProductDetail(id: productId)
} else if pathComponents.first == "profile",
pathComponents.count > 1 {
let username = pathComponents[1]
showProfile(username: username)
}
}
📋 Universal Links Setup
Pastikan: (1) File AASA di-host di HTTPS, (2) appIDs benar dengan Team ID, (3) Tidak ada redirect, (4) File < 128KB. Validasi di https://branch.io/resources/aasa-validator/
4. Android App Links
JSON — assetlinks.json
// File: https://myapp.com/.well-known/assetlinks.json
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.myapp",
"sha256_cert_fingerprints": [
"AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"
]
}
}]
XML — Android App Links Intent Filter
<activity android:name=".MainActivity"
android:launchMode="singleTask">
<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"
android:pathPrefix="/product" />
<data
android:scheme="https"
android:host="myapp.com"
android:pathPrefix="/profile" />
</intent-filter>
</activity>
Kotlin — App Links Handler
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleAppLink(intent)
}
private fun handleAppLink(intent: Intent?) {
if (intent?.action != Intent.ACTION_VIEW) return
val uri = intent.data ?: return
val navController = findNavController(R.id.nav_host)
when {
uri.pathSegments.firstOrNull() == "product" -> {
val id = uri.pathSegments.getOrNull(1) ?: return
navController.navigate(
R.id.productDetailFragment,
bundleOf("productId" to id)
)
}
uri.pathSegments.firstOrNull() == "profile" -> {
val username = uri.pathSegments.getOrNull(1) ?: return
navController.navigate(
R.id.profileFragment,
bundleOf("username" to username)
)
}
}
}
}
5. Deferred Deep Linking
Deferred deep linking mengarahkan user ke konten spesifik bahkan jika app belum terinstall. User diarahkan ke App Store dulu, lalu ke konten setelah install.
Dart — Flutter Firebase Dynamic Links
// Firebase Dynamic Links (deprecated, gunakan App Links + Branch)
// Alternatif: Branch.io SDK
// Install Branch
// pubspec.yaml: branch_sdk_flutter: ^latest
import 'package:branch_sdk_flutter/branch_sdk_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Init Branch
await BranchSdk.init(
enableLogging: true,
branchKey: 'key_live_xxxxx',
);
runApp(const MyApp());
}
// Create dynamic link
Future createShareLink(String productId) async {
BranchUniversalObject buo = BranchUniversalObject(
canonicalIdentifier: 'product/$productId',
title: 'Product $productId',
contentMetadata: BranchContentMetaData()
..addCustomMetadata('product_id', productId),
);
BranchLinkProperties linkProperties = BranchLinkProperties()
..addControlParameter('\$desktop_url', 'https://myapp.com/product/$productId')
..addControlParameter('\$android_url', 'https://play.google.com/store/apps/details?id=com.myapp')
..addControlParameter('\$ios_url', 'https://apps.apple.com/app/myapp/id123');
BranchResponse response = await BranchSdk.getShortUrl(buo, linkProperties);
return response.result;
}
// Handle incoming link
void listenForLinks() {
BranchSdk.initSession().listen((data) {
if (data.containsKey('product_id')) {
final productId = data['product_id'];
navigatorKey.currentState?.pushNamed('/product/$productId');
}
});
}
5.1 Deferred Deep Link Flow
| Step | Yang Terjadi |
|---|---|
| 1 | User klik link di email/ads/social media |
| 2 | Branch SDK merekam link + fingerprint device |
| 3 | User diarahkan ke App Store / Play Store |
| 4 | User install dan buka app |
| 5 | Branch SDK match fingerprint → retrieve data |
| 6 | App navigate ke konten yang tepat |
6. Deep Link Router
Dart — Flutter Router
import 'package:go_router/go_router.dart';
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (ctx, state) => const HomePage(),
),
GoRoute(
path: '/product/:id',
builder: (ctx, state) {
final id = state.pathParameters['id']!;
return ProductDetailPage(productId: id);
},
),
GoRoute(
path: '/profile/:username',
builder: (ctx, state) {
final username = state.pathParameters['username']!;
return ProfilePage(username: username);
},
),
GoRoute(
path: '/share/:code',
redirect: (ctx, state) async {
// Resolve share code ke actual path
final code = state.pathParameters['code']!;
final data = await api.resolveShareLink(code);
return '/product/${data.productId}';
},
builder: (ctx, state) => const SizedBox(), // placeholder
),
],
errorBuilder: (ctx, state) => NotFoundPage(url: state.uri.toString()),
);
7. Best Practices
| Praktik | Alasan |
|---|---|
| Gunakan App Links / Universal Links | Verified, auto-open tanpa dialog |
| Selalu sediakan fallback web | Jika app tidak terinstall |
| Handle deferred deep linking | Konversi install → konten spesifik |
| Test dengan URL validator | Pastikan AASA/assetlinks valid |
| Log semua deep link events | Analytics untuk marketing |
| Handle edge cases | Content deleted, user logged out, dll |