Mobile Development

Deep Linking & App Links

TOKEN

Deep Linking & App Links — URL schemes, Universal Links, Android App Links, deferred deep linking, routing, dan best practices

📋 Daftar Isi
  1. Apa itu Deep Linking
  2. URL Schemes
  3. Universal Links
  4. App Links
  5. Deferred Deep Linking
  6. Deep Link Router
  7. Best Practices
  8. Quiz Pemahaman

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
🌐
Universal Links
https://myapp.com/p/123
iOS, fallback ke web
🤖
App Links
https://myapp.com/p/123
Android, verified
MetodePlatformFallback WebVerifikasiTrust Level
URL SchemeKeduanyaTidakTidakRendah
Universal LinksiOS 9+Yaapple-app-site-associationTinggi
App LinksAndroid 6+Yaassetlinks.jsonTinggi
Intent FilterAndroidTidakTidakSedang

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
// 
//   
//     CFBundleURLSchemes
//     myapp
//     CFBundleURLName
//     com.myapp
//   
// 

// SceneDelegate.swift
func scene(_ scene: UIScene, openURLContexts URLContexts: Set) {
    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
    }
}
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/

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

StepYang Terjadi
1User klik link di email/ads/social media
2Branch SDK merekam link + fingerprint device
3User diarahkan ke App Store / Play Store
4User install dan buka app
5Branch SDK match fingerprint → retrieve data
6App 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()),
);
PraktikAlasan
Gunakan App Links / Universal LinksVerified, auto-open tanpa dialog
Selalu sediakan fallback webJika app tidak terinstall
Handle deferred deep linkingKonversi install → konten spesifik
Test dengan URL validatorPastikan AASA/assetlinks valid
Log semua deep link eventsAnalytics untuk marketing
Handle edge casesContent deleted, user logged out, dll

Quiz Pemahaman

Pertanyaan 1: Apa kelebihan Universal Links dibanding URL Scheme?

a) Lebih cepat
b) Verified dan punya fallback web
c) Lebih mudah setup
d) Support semua OS

Pertanyaan 2: File apa yang harus di-host untuk Universal Links iOS?

a) manifest.json
b) assetlinks.json
c) apple-app-site-association
d) well-known.json

Pertanyaan 3: Apa itu deferred deep linking?

a) Deep link yang delay
b) Mengarahkan ke konten setelah app diinstall
c) Deep link yang expired
d) Deep link tanpa app

Pertanyaan 4: Attribute apa di Android manifest untuk App Links?

a) android:enabled
b) android:autoVerify=true
c) android:exported
d) android:scheme

Pertanyaan 5: Platform apa yang digunakan untuk deferred deep linking?

a) Firebase Crashlytics
b) Branch.io
c) Google Analytics
d) App Store Connect
← SebelumnyaKembali ke Beranda Selanjutnya →Lihat Kategori
🔍 Zoom
100%
🎨 Tema