1. Pengenalan Capacitor
Capacitor adalah runtime native yang dikembangkan oleh tim di balik Ionic Framework. Capacitor memungkinkan Anda membungkus aplikasi web (HTML, CSS, JavaScript) menjadi aplikasi native untuk iOS, Android, dan desktop β sambil tetap memberikan akses penuh ke native APIs melalui plugin system.
Konsep dasar Capacitor sederhana: aplikasi web Anda adalah aplikasi native. Capacitor men-deploy web app ke dalam WebView native, lalu menyediakan jembatan (bridge) antara JavaScript dan native code. Berbeda dengan Cordova yang mengandalkan WebView bawaan platform, Capacitor menggunakan WebView modern yang lebih cepat dan mendukung fitur web terbaru.
Keunggulan Capacitor
| Keunggulan | Penjelasan |
|---|---|
| Framework Agnostic | Bisa digunakan dengan React, Vue, Angular, Svelte, atau vanilla JS |
| Native Project Access | Project native (Xcode, Android Studio) bisa di-commit ke source control |
| NPM Plugin System | Native plugins sebagai npm packages β install seperti library biasa |
| Web Standards | Menggunakan standar web β tidak ada runtime custom seperti Cordova |
| PWA Ready | Bisa juga di-deploy sebagai Progressive Web App |
| Live Reload | Hot reload langsung di device selama development |
| Native IDE Support | Buka Xcode/Android Studio langsung untuk native customization |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β YOUR WEB APPLICATION β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β β HTML + CSS + JavaScript (React/Vue/Angular) β β β βββββββββββββββββββββββββ¬ββββββββββββββββββββββββββ β β βΌ β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β β Capacitor JS Bridge (@capacitor/core) β β β β ββββββββββ ββββββββββββ ββββββββββ ββββββββββ β β β β βCamera β βGeolocationβ βStorageβ βPush Notβ β β β β βPlugin β β Plugin β βPlugin β βPlugin β β β β β βββββ¬βββββ ββββββ¬ββββββ βββββ¬ββββ βββββ¬βββββ β β β ββββββββΌββββββββββββΌβββββββββββΌββββββββββΌββββββββ β β βΌ βΌ βΌ βΌ β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β β Native Platform Runtime β β β β iOS: WKWebView Android: Chrome WebView β β β βββββββββββββββββββββββββ¬ββββββββββββββββββββββββββ β β βΌ β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β β iOS / Android / Desktop Native APIs β β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
2. Cordova vs Capacitor
Apache Cordova adalah pendahulu Capacitor. Keduanya membungkus web app menjadi native, tetapi memiliki filosofi dan pendekatan yang berbeda secara signifikan.
| Aspek | Cordova | Capacitor |
|---|---|---|
| Developer | Apache Foundation | Ionic Team (Max Lynch) |
| Native Project | Dihidupkan saat build, tidak di-commit | Native project di-commit ke source control |
| WebView | Platform default (bisa outdated) | WKWebView (iOS) / Chrome WebView (Android) |
| Plugin System | npm + Cordova plugin registry | npm + Capacitor plugin registry |
| Native IDE | Minimal usage | Full support β buka Xcode/Android Studio kapan saja |
| Progressive | Sulit upgrade ke native | Mudah: web β hybrid β native bertahap |
| PWA Support | Tidak built-in | Built-in, bisa deploy sebagai PWA juga |
| Status | Maintenance mode | Active development, masa depan hybrid |
Capacitor kompatibel dengan sebagian besar plugin Cordova. Anda bisa menggunakan @capacitor-community/cordova-compat untuk memakai plugin Cordova yang belum ada versi Capacitor-nya. Migrasi dari Cordova ke Capacitor cukup straightforward untuk sebagian besar proyek.
3. Setup & Inisialisasi
# ===== OPSI 1: Proyek baru dengan Ionic ===== npm install -g @ionic/cli ionic start my-app tabs --type=react cd my-app # ===== OPSI 2: Tambahkan ke proyek web yang sudah ada ===== cd existing-web-app # 1. Install Capacitor core & CLI npm install @capacitor/core @capacitor/cli # 2. Inisialisasi Capacitor npx cap init # β App name: My Awesome App # β App ID: com.beebane.myapp # β Web asset directory: dist (atau build untuk CRA) # 3. Tambahkan platform npx cap add android npx cap add ios # 4. Build web app npm run build # Hasil di folder dist/ # 5. Sync web code ke native project npx cap sync # 6. Buka di native IDE npx cap open android # Buka Android Studio npx cap open ios # Buka Xcode # 7. Jalankan dari CLI npx cap run android npx cap run ios npx cap run android --livereload --external # Live reload di device
Konfigurasi capacitor.config.ts
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.beebane.myapp',
appName: 'My Awesome App',
webDir: 'dist', // Folder output build web (dist, build, public)
// Server configuration
server: {
// URL untuk live reload (development)
// url: 'http://192.168.1.100:5173',
// cleartext: true,
// Android: mengizinkan HTTP cleartext (development only)
androidScheme: 'https',
},
// Android-specific config
android: {
// Mengizinkan mixed content (HTTP di HTTPS page)
allowMixedContent: true,
// Capture input untuk debugging
captureInput: true,
// Web debugging
webContentsDebuggingEnabled: true,
},
// iOS-specific config
ios: {
// Content inset behavior
contentInset: 'automatic',
// Mengizinkan link navigasi
scheme: 'MyApp',
},
// Plugin configurations
plugins: {
SplashScreen: {
launchShowDuration: 2000,
backgroundColor: '#1a1a2e',
showSpinner: true,
spinnerColor: '#e94560',
androidScaleType: 'CENTER_CROP',
splashFullScreen: true,
splashImmersive: true,
},
StatusBar: {
style: 'DARK',
backgroundColor: '#1a1a2e',
},
Keyboard: {
resize: 'body',
style: 'DARK',
resizeOnFullScreen: true,
},
PushNotifications: {
presentationOptions: ['badge', 'sound', 'alert'],
},
},
};
export default config;
4. Integrasi dengan Ionic
Ionic Framework adalah UI toolkit yang menyediakan komponen UI siap pakai yang terlihat native di setiap platform. Ketika dipasangkan dengan Capacitor, Ionic menyediakan fondasi UI yang solid sementara Capacitor menangani akses ke native APIs.
import React, { useState } from 'react';
import {
IonApp, IonHeader, IonToolbar, IonTitle, IonContent,
IonButton, IonCard, IonCardHeader, IonCardTitle,
IonCardContent, IonList, IonItem, IonLabel, IonInput,
IonToggle, IonIcon, IonTabs, IonTabBar, IonTabButton,
IonBadge, IonFab, IonFabButton, IonModal, IonAlert,
IonLoading, IonRefresher, IonRefresherContent,
IonSearchbar, IonSegment, IonSegmentButton,
setupIonicReact
} from '@ionic/react';
import {
home, search, cart, person, add,
heart, share, star
} from 'ionicons/icons';
// Initialize Ionic
setupIonicReact({
mode: 'md', // 'md' = Material Design, 'ios' = iOS style
});
// ===== IONIC APP WITH TABS =====
function App() {
return (
<IonApp>
<IonTabs>
<IonContent>
{/* Tab content handled by router */}
</IonContent>
<IonTabBar slot="bottom">
<IonTabButton tab="home" href="/home">
<IonIcon icon={home} />
<IonLabel>Beranda</IonLabel>
</IonTabButton>
<IonTabButton tab="search" href="/search">
<IonIcon icon={search} />
<IonLabel>Cari</IonLabel>
</IonTabButton>
<IonTabButton tab="cart" href="/cart">
<IonIcon icon={cart} />
<IonLabel>Keranjang</IonLabel>
<IonBadge color="danger">3</IonBadge>
</IonTabButton>
<IonTabButton tab="profile" href="/profile">
<IonIcon icon={person} />
<IonLabel>Profil</IonLabel>
</IonTabButton>
</IonTabBar>
</IonTabs>
</IonApp>
);
}
// ===== CONTOH HALAMAN =====
function HomePage() {
const [showModal, setShowModal] = useState(false);
const [searchText, setSearchText] = useState('');
return (
<>
<IonHeader>
<IonToolbar color="primary">
<IonTitle>Beranda</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent fullscreen>
<IonSearchbar
value={searchText}
onIonInput={(e) => setSearchText(e.detail.value!)}
placeholder="Cari produk..."
/>
<IonCard>
<IonCardHeader>
<IonCardTitle>Selamat Datang! π</IonCardTitle>
</IonCardHeader>
<IonCardContent>
Jelajahi produk terbaru kami dan temukan yang Anda suka.
<IonButton expand="block" onClick={() => setShowModal(true)}>
Mulai Belanja
</IonButton>
</IonCardContent>
</IonCard>
<IonList>
{[1, 2, 3, 4, 5].map((i) => (
<IonItem key={i} button detail>
<IonLabel>
<h2>Produk {i}</h2>
<p>Deskripsi singkat produk {i}</p>
</IonLabel>
<IonIcon icon={heart} slot="end" color="medium" />
</IonItem>
))}
</IonList>
{/* Floating Action Button */}
<IonFab vertical="bottom" horizontal="end" slot="fixed">
<IonFabButton>
<IonIcon icon={add} />
</IonFabButton>
</IonFab>
{/* Modal */}
<IonModal isOpen={showModal}>
<IonHeader>
<IonToolbar>
<IonTitle>Detail</IonTitle>
<IonButton slot="end" onClick={() => setShowModal(false)}>
Tutup
</IonButton>
</IonToolbar>
</IonHeader>
<IonContent className="ion-padding">
<p>Konten modal di sini...</p>
</IonContent>
</IonModal>
</IonContent>
</>
);
}
5. Native Plugins
Capacitor menggunakan sistem plugin yang memungkinkan Anda mengakses native APIs dari JavaScript. Plugin diinstal sebagai npm packages, lalu di-sync ke native project.
# Core plugins (official) npm install @capacitor/camera npm install @capacitor/geolocation npm install @capacitor/filesystem npm install @capacitor/preferences npm install @capacitor/haptics npm install @capacitor/share npm install @capacitor/local-notifications npm install @capacitor/push-notifications npm install @capacitor/browser npm install @capacitor/device npm install @capacitor/network npm install @capacitor/screen-reader npm install @capacitor/battery npm install @capacitor/app # Community plugins npm install @capacitor-community/barcode-scanner npm install @capacitor-community/sqlite npm install @capacitor-community/camera-preview npm install @capacitor-community/background-geolocation # Setelah install, sync ke native project npx cap sync
// ===== CAMERA =====
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
async function takePicture() {
try {
const image = await Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.Uri, // Uri, Base64, DataUrl
source: CameraSource.Camera, // Camera, Photos, Prompt
});
// image.webPath β URL untuk ditampilkan di web
// image.format β jpeg, png, webp
console.log('Foto:', image.webPath);
return image.webPath;
} catch (error) {
console.error('Gagal ambil foto:', error);
}
}
// ===== GEOLOCATION =====
import { Geolocation } from '@capacitor/geolocation';
async function getCurrentLocation() {
try {
// Minta izin
const permission = await Geolocation.requestPermissions();
// Dapatkan posisi
const position = await Geolocation.getCurrentPosition({
enableHighAccuracy: true,
timeout: 10000,
});
return {
lat: position.coords.latitude,
lng: position.coords.longitude,
accuracy: position.coords.accuracy,
};
} catch (error) {
console.error('Gagal mendapatkan lokasi:', error);
}
}
// Watch position (tracking)
function watchLocation(callback: (pos: any) => void) {
const watchId = Geolocation.watchPosition(
{ enableHighAccuracy: true },
(position, error) => {
if (error) {
console.error('Watch error:', error);
return;
}
callback(position?.coords);
}
);
return watchId; // Simpan untuk di-clear nanti
}
// ===== FILE SYSTEM =====
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
async function fileOperations() {
// Tulis file
await Filesystem.writeFile({
path: 'data/config.json',
data: JSON.stringify({ theme: 'dark', lang: 'id' }),
directory: Directory.Documents,
encoding: Encoding.UTF8,
});
// Baca file
const contents = await Filesystem.readFile({
path: 'data/config.json',
directory: Directory.Documents,
encoding: Encoding.UTF8,
});
console.log('Isi file:', contents.data);
// Hapus file
await Filesystem.deleteFile({
path: 'data/config.json',
directory: Directory.Documents,
});
// List direktori
const files = await Filesystem.readdir({
path: 'data',
directory: Directory.Documents,
});
console.log('Files:', files.files);
}
// ===== PREFERENCES (Key-Value Storage) =====
import { Preferences } from '@capacitor/preferences';
async function storageOperations() {
// Simpan
await Preferences.set({
key: 'user_token',
value: 'abc123xyz',
});
// Baca
const { value } = await Preferences.get({ key: 'user_token' });
console.log('Token:', value);
// Hapus
await Preferences.remove({ key: 'user_token' });
// Ambil semua keys
const { keys } = await Preferences.keys();
console.log('Semua keys:', keys);
// Clear semua
await Preferences.clear();
}
// ===== PUSH NOTIFICATIONS =====
import {
PushNotifications,
Token,
PushNotificationSchema,
ActionPerformed
} from '@capacitor/push-notifications';
async function setupPushNotifications() {
// Minta izin
const permission = await PushNotifications.requestPermissions();
if (permission.receive !== 'granted') {
console.log('Izin notifikasi ditolak');
return;
}
// Register untuk push notifications
await PushNotifications.register();
// Listener: token diterima
PushNotifications.addListener('registration', (token: Token) => {
console.log('Push token:', token.value);
// Kirim token ke server Anda
sendTokenToServer(token.value);
});
// Listener: notifikasi diterima (app foreground)
PushNotifications.addListener(
'pushNotificationReceived',
(notification: PushNotificationSchema) => {
console.log('Notifikasi diterima:', notification);
// Tampilkan custom UI
}
);
// Listener: notifikasi di-tap
PushNotifications.addListener(
'pushNotificationActionPerformed',
(action: ActionPerformed) => {
console.log('Notifikasi di-tap:', action.notification);
// Navigate ke halaman yang sesuai
}
);
}
// ===== NETWORK STATUS =====
import { Network } from '@capacitor/network';
async function checkNetwork() {
const status = await Network.getStatus();
console.log('Connected:', status.connected);
console.log('Type:', status.connectionType); // wifi, cellular, none
// Monitor perubahan
Network.addListener('networkStatusChange', (status) => {
console.log('Network changed:', status.connected);
if (!status.connected) {
showOfflineMessage();
}
});
}
// ===== HAPTICS =====
import { Haptics, ImpactStyle } from '@capacitor/haptics';
async function hapticFeedback() {
// Impact feedback
await Haptics.impact({ style: ImpactStyle.Light });
await Haptics.impact({ style: ImpactStyle.Medium });
await Haptics.impact({ style: ImpactStyle.Heavy });
// Notification feedback
await Haptics.notification({ type: 'SUCCESS' });
// Vibrate pattern
await Haptics.vibrate({ duration: 200 });
}
6. Platform-Specific Code
import { Capacitor } from '@capacitor/core';
import { Device } from '@capacitor/device';
// Deteksi platform
function getPlatform() {
// Capacitor.isNativePlatform β true jika di native app
// Capacitor.getPlatform() β 'ios', 'android', 'web'
if (Capacitor.isNativePlatform()) {
return Capacitor.getPlatform(); // 'ios' atau 'android'
}
return 'web';
}
// Conditional code berdasarkan platform
async function platformSpecificLogic() {
const platform = Capacitor.getPlatform();
if (platform === 'ios') {
// iOS-specific code
console.log('Running on iOS');
// Misal: gunakan SF Symbols, iOS-style alert
} else if (platform === 'android') {
// Android-specific code
console.log('Running on Android');
// Misal: gunakan Material icons, back button handling
} else {
// Web fallback
console.log('Running on Web');
}
}
// Device info
async function getDeviceInfo() {
const info = await Device.getInfo();
return {
model: info.model,
platform: info.platform,
osVersion: info.osVersion,
manufacturer: info.manufacturer,
isVirtual: info.isVirtual, // Emulator?
batteryLevel: (await Device.getBatteryInfo()).batteryLevel,
};
}
// Handle Android back button
import { App } from '@capacitor/app';
App.addListener('backButton', ({ canGoBack }) => {
if (!canGoBack) {
App.exitApp();
} else {
window.history.back();
}
});
// App state changes
App.addListener('appStateChange', ({ isActive }) => {
if (isActive) {
console.log('App foreground');
// Refresh data, resume timers
} else {
console.log('App background');
// Save state, pause operations
}
});
// Deep links
App.addListener('appUrlOpen', (event) => {
console.log('Deep link:', event.url);
// Parse URL dan navigate ke halaman yang sesuai
const path = new URL(event.url).pathname;
router.push(path);
});
7. Capacitor APIs
// ===== SHARE =====
import { Share } from '@capacitor/share';
async function shareContent() {
await Share.share({
title: 'Lihat produk ini!',
text: 'Produk keren yang saya temukan di BeebaneLabs',
url: 'https://beebanelabs.pages.dev/products/123',
dialogTitle: 'Bagikan ke teman',
});
}
// ===== BROWSER (In-App) =====
import { Browser } from '@capacitor/browser';
async function openLink(url: string) {
// Buka di in-app browser (bukan external browser)
await Browser.open({ url });
// Listener saat browser ditutup
Browser.addListener('browserFinished', () => {
console.log('Browser ditutup');
});
}
// ===== CLIPBOARD =====
import { Clipboard } from '@capacitor/clipboard';
async function clipboardOps() {
// Copy
await Clipboard.write({
string: 'Teks yang disalin',
});
// Paste
const { value } = await Clipboard.read();
console.log('Clipboard:', value);
}
// ===== SCREEN ORIENTATION =====
import { ScreenOrientation } from '@capacitor/screen-orientation';
async function lockOrientation() {
// Lock ke portrait
await ScreenOrientation.lock({ orientation: 'portrait' });
// Lock ke landscape
// await ScreenOrientation.lock({ orientation: 'landscape' });
// Unlock
await ScreenOrientation.unlock();
// Listen perubahan
ScreenOrientation.addListener('screenOrientationChange', (orientation) => {
console.log('Orientation:', orientation.type);
});
}
// ===== SPLASH SCREEN =====
import { SplashScreen } from '@capacitor/splash-screen';
async function hideSplash() {
// Sembunyikan splash screen setelah app siap
await SplashScreen.hide();
}
async function showSplash() {
// Tampilkan splash screen lagi (loading state)
await SplashScreen.show({
showDuration: 2000,
autoHide: true,
});
}
// ===== STATUS BAR =====
import { StatusBar, Style } from '@capacitor/status-bar';
async function configureStatusBar() {
// Set style
await StatusBar.setStyle({ style: Style.Dark }); // Light text
// await StatusBar.setStyle({ style: Style.Light }); // Dark text
// Set warna background
await StatusBar.setBackgroundColor({ color: '#1a1a2e' });
// Sembunyikan (full-screen)
// await StatusBar.hide();
// Tampilkan kembali
// await StatusBar.show();
}
8. PWA & Web Deployment
Salah satu keunggulan Capacitor adalah kemampuan untuk meng-deploy aplikasi yang sama sebagai Progressive Web App (PWA) di web. PWA memungkinkan instalasi di home screen, offline support, dan push notifications di browser.
// vite.config.ts β Vite PWA plugin
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({
plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['favicon.ico', 'apple-touch-icon.png'],
manifest: {
name: 'My Awesome App',
short_name: 'MyApp',
description: 'Aplikasi mobile hybrid dengan Capacitor',
theme_color: '#1a1a2e',
background_color: '#ffffff',
display: 'standalone',
orientation: 'portrait',
start_url: '/',
icons: [
{
src: 'pwa-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: 'pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'any maskable',
},
],
},
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
runtimeCaching: [
{
urlPattern: /^https:\/\/api\.example\.com\/.*/i,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: { maxEntries: 50, maxAgeSeconds: 300 },
},
},
],
},
}),
],
});
9. Build & Deployment
# ===== DEVELOPMENT WORKFLOW ===== # 1. Build web app npm run build # 2. Sync ke native project npx cap sync # 3. Live reload di device (development) npx cap run android --livereload --external npx cap run ios --livereload --external # ===== PRODUCTION BUILD ===== # 4. Build web app (production mode) npm run build # 5. Copy & sync ke native npx cap copy npx cap sync # 6. Build native # Android: cd android ./gradlew assembleDebug # Debug APK ./gradlew assembleRelease # Release APK/AAB cd .. # iOS: # Buka Xcode β Product β Archive npx cap open ios # ===== VERSIONING ===== # Update versi di package.json, lalu: npx cap sync # Pastikan version di Android (build.gradle) dan iOS (Xcode) match # ===== TIPS ===== # Selalu jalankan cap sync setelah: # - Install/update plugin baru # - Mengubah capacitor.config.ts # - Mengubah native code (jika ada) # - Build web app dengan perubahan signifikan # Cek status npx cap doctor # Diagnosa masalah
| Langkah | Android | iOS |
|---|---|---|
| Build | ./gradlew assembleRelease | Xcode β Archive |
| Signing | Keystore (.jks) | Provisioning Profile + Certificate |
| Upload | Google Play Console | App Store Connect / Transporter |
| Review | 1-3 hari | 1-7 hari |
10. Quiz Pemahaman
1. Apa perbedaan utama Capacitor dan Cordova?
2. Apa fungsi npx cap sync?
3. Keunggulan Capacitor dibanding framework-specific solution?
4. Apa yang dimaksud dengan PWA?
5. Kapan perlu menjalankan cap sync?
Anda telah mempelajari Capacitor untuk membangun hybrid mobile apps β dari konsep dasar, setup, native plugins, Ionic integration, platform-specific code, PWA, hingga deployment. Capacitor adalah jembatan sempurna antara web dan native!