playbook/antigravity-awesome-skills/skills/android-dev/references/hybrid.md

4.2 KiB

Hybrid Android Reference (Capacitor + Ionic / React)

When to Use Hybrid

Good fit:

  • Web team building a companion Android app
  • Content-heavy apps (news, docs, forms)
  • PWA upgrade to installable app
  • Rapid prototyping

Avoid for:

  • Real-time games / heavy animations
  • Deep native sensor / hardware access
  • Apps requiring 60fps custom animations
  • Bluetooth/NFC intensive apps (use plugins, but complex)

Stack Options

Option UI Framework Best For
Capacitor + Ionic Ionic components Full mobile-optimized UI
Capacitor + React React + Tailwind Web team reuse
Capacitor + Vue Vue + Ionic Vue teams
Capacitor + Angular Angular + Ionic Enterprise Angular teams

Project Structure (Capacitor + React)

src/
├── App.tsx
├── pages/                # Screen components
├── components/           # Shared UI components
├── hooks/                # Business logic hooks
├── services/             # API, storage services
└── store/                # State management
android/                  # Native Android project (generated)
├── app/src/main/
│   ├── AndroidManifest.xml
│   └── java/.../MainActivity.kt
capacitor.config.ts       # Capacitor configuration

Capacitor Config

// capacitor.config.ts
import { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.example.app',
  appName: 'My App',
  webDir: 'dist',
  server: {
    androidScheme: 'https',
  },
  android: {
    buildOptions: {
      releaseType: 'APK', // or AAB for Play Store
    },
  },
  plugins: {
    SplashScreen: {
      launchShowDuration: 0,
      backgroundColor: '#FFFFFF',
    },
    PushNotifications: {
      presentationOptions: ['badge', 'sound', 'alert'],
    },
  },
};

Native Plugin Usage

import { Camera, CameraResultType } from '@capacitor/camera';
import { Preferences } from '@capacitor/preferences';
import { PushNotifications } from '@capacitor/push-notifications';
import { Geolocation } from '@capacitor/geolocation';

// Camera
const takePhoto = async () => {
  const photo = await Camera.getPhoto({
    quality: 90,
    allowEditing: false,
    resultType: CameraResultType.Uri,
  });
  return photo.webPath;
};

// Secure storage
const saveToken = async (token: string) => {
  await Preferences.set({ key: 'auth_token', value: token });
};

const getToken = async (): Promise<string | null> => {
  const { value } = await Preferences.get({ key: 'auth_token' });
  return value;
};

// Push notifications
const initPush = async () => {
  const permission = await PushNotifications.requestPermissions();
  if (permission.receive === 'granted') {
    await PushNotifications.register();
  }
  PushNotifications.addListener('registration', ({ value: token }) => {
    console.log('FCM Token:', token);
  });
};

Performance Best Practices

  • Ensure hardware acceleration is enabled for the application in AndroidManifest.xml (default in Capacitor)
  • Enable HTTP caching in Android WebView settings
  • Lazy-load routes with React.lazy / dynamic imports
  • Avoid setTimeout/setInterval for animations; use CSS transitions
  • Use @ionic/react components — they handle mobile-specific touch handling
  • Ionic virtual scroll for long lists

Build & Deploy

# Build web assets
npm run build

# Sync to native
npx cap sync android

# Open in Android Studio
npx cap open android

# Build release APK/AAB via Android Studio or:
cd android && ./gradlew bundleRelease

Custom Native Plugin (when built-in plugins don't cover it)

// android/app/src/main/java/.../MyPlugin.kt
@CapacitorPlugin(name = "MyPlugin")
class MyPlugin : Plugin() {
    @PluginMethod
    fun doNativeWork(call: PluginCall) {
        val value = call.getString("input") ?: return call.reject("No input")
        // Do native work
        val result = JSObject()
        result.put("output", "processed: $value")
        call.resolve(result)
    }
}

// TypeScript usage
import { registerPlugin } from '@capacitor/core';
const MyPlugin = registerPlugin<{ doNativeWork: (opts: { input: string }) => Promise<{ output: string }> }>('MyPlugin');
const result = await MyPlugin.doNativeWork({ input: 'hello' });