Mobile

Capacitor: Web to Mobile

TOKEN

Panduan lengkap membangun hybrid mobile apps dengan Capacitor β€” dari setup, native plugins, platform-specific code, hingga deployment ke App Store dan Google Play

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

KeunggulanPenjelasan
Framework AgnosticBisa digunakan dengan React, Vue, Angular, Svelte, atau vanilla JS
Native Project AccessProject native (Xcode, Android Studio) bisa di-commit ke source control
NPM Plugin SystemNative plugins sebagai npm packages β€” install seperti library biasa
Web StandardsMenggunakan standar web β€” tidak ada runtime custom seperti Cordova
PWA ReadyBisa juga di-deploy sebagai Progressive Web App
Live ReloadHot reload langsung di device selama development
Native IDE SupportBuka Xcode/Android Studio langsung untuk native customization
Diagram: Arsitektur Capacitor
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                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.

AspekCordovaCapacitor
DeveloperApache FoundationIonic Team (Max Lynch)
Native ProjectDihidupkan saat build, tidak di-commitNative project di-commit ke source control
WebViewPlatform default (bisa outdated)WKWebView (iOS) / Chrome WebView (Android)
Plugin Systemnpm + Cordova plugin registrynpm + Capacitor plugin registry
Native IDEMinimal usageFull support β€” buka Xcode/Android Studio kapan saja
ProgressiveSulit upgrade ke nativeMudah: web β†’ hybrid β†’ native bertahap
PWA SupportTidak built-inBuilt-in, bisa deploy sebagai PWA juga
StatusMaintenance modeActive development, masa depan hybrid
πŸ’‘ Migrasi dari Cordova

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

Bash β€” Setup Capacitor
# ===== 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

TypeScript β€” 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.

JSX β€” Ionic + React + Capacitor
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.

Bash β€” Install Plugins
# 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
TypeScript β€” Menggunakan Plugins
// ===== 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

TypeScript β€” Platform Detection
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

TypeScript β€” More 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.

JavaScript β€” PWA Setup
// 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

Bash β€” Build & Deploy
# ===== 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
LangkahAndroidiOS
Build./gradlew assembleReleaseXcode β†’ Archive
SigningKeystore (.jks)Provisioning Profile + Certificate
UploadGoogle Play ConsoleApp Store Connect / Transporter
Review1-3 hari1-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?

πŸŽ‰ Selamat!

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!

πŸ” Zoom
100%
🎨 Tema