☀️Siang
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

Artikel: Deep Linking Artikel: 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
🌐
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
    }
}
 ad-slot-wide"> 
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