Memahami custom scheme vs App Links (Android) vs Universal Links (iOS), konfigurasi `assetlinks.json` dan `apple-app-site-association`, lifecycle events `pause`/`resume`, serta praktik membuka konten spesifik dari link eksternal.

Setelah di episode 8 kita menguasai Push & Local Notifications, pada episode ini kita membahas cara membuka aplikasi dari link eksternal — deep linking. Ini adalah fitur fundamental yang menghubungkan web dan mobile: ketika user mengklik link di browser, email, atau QR code, aplikasi terbuka langsung ke konten yang relevan.
Mengapa deep linking penting? Karena tanpa deep linking, user harus membuka app lalu navigasi manual ke konten. Deep linking menghilangkan frisi ini dan meningkatkan engagement secara signifikan — terutama untuk marketing, sharing, dan notifikasi.
Cara paling sederhana: mendaftarkan skema URL kustom untuk aplikasi.
myapp://article/123Konfigurasi di capacitor.config.ts:
const config: CapacitorConfig = {
// ...
server: {
url: 'http://192.168.1.100:5173',
androidScheme: 'https',
},
};Kelebihan: mudah setup, langsung fungsi. Kekurangan: tidak aman (app lain bisa register skema yang sama), tidak bisa digunakan di web.
Menggunakan domain HTTPS yang dimiliki kalian. Ketika user mengklik link di iOS, sistem secara otomatis membuka aplikasi jika terinstall.
https://myapp.com/article/123Konfigurasi: buat file apple-app-site-association di root domain kalian:
{
"applinks": {
"apps": [],
"details": [
{
"appIDs": ["TEAMID.com.example.myapp"],
"paths": ["/article/*", "/profile/*"]
}
]
}
}Upload file ini ke https://myapp.com/.well-known/apple-app-site-association.
Mirip dengan Universal Links, tetapi untuk Android. Membutuhkan file assetlinks.json di domain kalian:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.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"
]
}
}
]Upload ke https://myapp.com/.well-known/assetlinks.json.
| Fitur | Custom Scheme | Universal Links (iOS) | App Links (Android) |
|---|---|---|---|
| Keamanan | Rendah | Tinggi | Tinggi |
| Work di web | Tidak | Ya | Ya |
| Setup | Mudah | Menengah | Menengah |
| Fallback ke browser | Tidak | Ya | Ya |
Tip
Untuk production, gunakan Universal Links (iOS) + App Links (Android) karena lebih aman dan profesional. Custom scheme cukup untuk development atau app internal.
import { App } from '@capacitor/app';
// Dengarkan URL yang membuka app
App.addListener('appUrlOpen', (event) => {
console.log('App opened via URL:', event.url);
// Parse URL dan navigasi
const url = new URL(event.url);
const path = url.pathname;
if (path.startsWith('/article/')) {
const articleId = path.split('/')[2];
navigateToArticle(articleId);
} else if (path.startsWith('/profile/')) {
const userId = path.split('/')[2];
navigateToProfile(userId);
}
});Aplikasi mobile memiliki lifecycle yang berbeda dari browser tab:
import { App } from '@capacitor/app';
// App masuk/keluar background
App.addListener('appStateChange', ({ isActive }) => {
if (isActive) {
// App di foreground — refresh data, resume animasi
refreshData();
} else {
// App di background — pause, save state, release resources
saveState();
}
});
// Back button (Android only)
App.addListener('backButton', ({ canGoBack }) => {
if (canGoBack) {
window.history.back();
} else {
// Tampilkan dialog exit atau minimize
showExitDialog();
}
});
// URL open
App.addListener('appUrlOpen', (event) => {
handleDeepLink(event.url);
});
// Open from another app (restoration)
App.addListener('appRestoredResult', (result) => {
console.log('Restored state:', result);
});Note
Android bisa meng-kill aplikasi di background kapan saja jika membutuhkan memory. Selalu simpan state penting ke Preferences atau database saat masuk background.
Kombinasikan deep link + lifecycle untuk UX yang mulus:
import { App } from '@capacitor/app';
import { Preferences } from '@capacitor/preferences';
class DeepLinkHandler {
static init() {
// Handle URL yang membuka app
App.addListener('appUrlOpen', async (event) => {
const url = new URL(event.url);
// Simpan deep link untuk recovery
await Preferences.set({
key: 'pendingDeepLink',
value: url.pathname,
});
// Navigasi jika app sudah aktif
if (document.visibilityState === 'visible') {
this.navigate(url.pathname);
}
});
// Handle restore setelah killed
App.addListener('appRestoredResult', async () => {
const pending = await Preferences.get({ key: 'pendingDeepLink' });
if (pending.value) {
this.navigate(pending.value);
await Preferences.remove({ key: 'pendingDeepLink' });
}
});
}
static navigate(path: string) {
// Router navigasi berdasarkan path
window.history.pushState({}, '', path);
}
}Pada episode 9 ini, kalian telah memahami:
Di episode 10 selanjutnya, kita akan membahas Networking: CapacitorHttp & CORS — masalah CORS di WebView, solusi CapacitorHttp yang mem-patch fetch/XHR ke native HTTP, cookie management, dan konsumsi API third-party tanpa proxy. Sampai jumpa!