🎯 Recommended Samples
Balanced sample collections from various categories for you to explore
Capacitor Hybrid App Development Samples
Capacitor hybrid app development examples including native device APIs, plugins, cross-platform deployment, and Progressive Web App capabilities
💻 Capacitor Native Device APIs typescript
🟢 simple
Implement native device features using Capacitor plugins including camera, geolocation, notifications, and file system access
// capacitor.config.ts
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'Device API Demo',
webDir: 'dist',
bundledWebRuntime: false,
plugins: {
Camera: {
permissions: ['camera', 'photos']
},
Geolocation: {
permissions: ['location']
},
PushNotifications: {
presentationOptions: ['badge', 'sound', 'alert']
},
LocalNotifications: {
smallIcon: 'ic_stat_icon_config_sample',
iconColor: '#488AFF',
sound: 'beep.wav'
},
SplashScreen: {
launchShowDuration: 3000,
launchAutoHide: true,
backgroundColor: '#3880ff',
androidSplashResourceName: 'splash',
androidScaleType: 'CENTER_CROP',
showSpinner: true,
androidSpinnerStyle: 'large',
iosSpinnerStyle: 'small',
spinnerColor: '#999999',
splashFullScreen: true,
splashImmersive: true,
layoutName: 'launch_screen',
useDialog: true
}
}
};
export default config;
// src/services/DeviceService.ts
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Geolocation, Position } from '@capacitor/geolocation';
import { LocalNotifications, LocalNotificationSchema } from '@capacitor/local-notifications';
import { PushNotifications, PushNotificationSchema, PermissionStatus } from '@capacitor/push-notifications';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Share } from 'capacitor-share';
export interface DevicePosition {
latitude: number;
longitude: number;
accuracy: number;
timestamp: number;
}
export interface CameraPhoto {
filepath: string;
webviewPath?: string;
base64?: string;
}
export class DeviceService {
// Camera Operations
static async takePhoto(): Promise<CameraPhoto> {
try {
const photo: Photo = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 90,
allowEditing: true,
promptLabelHeader: 'Take a Photo',
promptLabelCancel: 'Cancel',
promptLabelPhoto: 'From Gallery',
promptLabelPicture: 'Take Photo'
});
return {
filepath: photo.path || '',
webviewPath: photo.webPath
};
} catch (error) {
console.error('Camera error:', error);
throw new Error('Failed to take photo');
}
}
static async selectFromGallery(): Promise<CameraPhoto> {
try {
const photo: Photo = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Photos,
quality: 90
});
return {
filepath: photo.path || '',
webviewPath: photo.webPath
};
} catch (error) {
console.error('Gallery error:', error);
throw new Error('Failed to select photo from gallery');
}
}
// Geolocation Operations
static async getCurrentPosition(): Promise<DevicePosition> {
try {
const coordinates: Position = await Geolocation.getCurrentPosition({
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
});
return {
latitude: coordinates.coords.latitude,
longitude: coordinates.coords.longitude,
accuracy: coordinates.coords.accuracy,
timestamp: coordinates.timestamp
};
} catch (error) {
console.error('Geolocation error:', error);
throw new Error('Failed to get current position');
}
}
static watchPosition(
callback: (position: DevicePosition) => void
): Promise<string> {
return Geolocation.watchPosition(
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
},
(position, err) => {
if (err) {
console.error('Position watch error:', err);
return;
}
if (position) {
callback({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
timestamp: position.timestamp
});
}
}
);
}
// Notification Operations
static async requestNotificationPermission(): Promise<boolean> {
try {
const permStatus: PermissionStatus = await PushNotifications.requestPermissions();
return permStatus.receive === 'granted';
} catch (error) {
console.error('Notification permission error:', error);
return false;
}
}
static async scheduleLocalNotification(
title: string,
body: string,
schedule?: { at: Date },
id: number = Date.now()
): Promise<void> {
try {
const notification: LocalNotificationSchema = {
id,
title,
body,
schedule,
sound: 'beep.wav',
smallIcon: 'ic_stat_icon_config_sample',
iconColor: '#488AFF',
actionTypeId: ''
};
await LocalNotifications.schedule({
notifications: [notification]
});
} catch (error) {
console.error('Schedule notification error:', error);
throw new Error('Failed to schedule notification');
}
}
static async sendPushNotification(
to: string,
title: string,
body: string
): Promise<void> {
// This would typically be handled by your backend service
// Here's an example of how to register for push notifications
try {
await PushNotifications.register();
PushNotifications.addListener('registration', (token) => {
console.log('Push registration success, token: ' + token.value);
// Send this token to your backend
});
PushNotifications.addListener('registrationError', (error) => {
console.error('Error on registration: ' + JSON.stringify(error.error));
});
PushNotifications.addListener(
'pushNotificationReceived',
(notification: PushNotificationSchema) => {
console.log('Push notification received: ', notification);
}
);
} catch (error) {
console.error('Push notification error:', error);
}
}
// File System Operations
static async savePhotoToFile(photo: CameraPhoto): Promise<string> {
try {
const base64Data = await this.readAsBase64(photo);
const fileName = new Date().getTime() + '.jpeg';
const savedFile = await Filesystem.writeFile({
path: fileName,
data: base64Data,
directory: Directory.Data
});
return savedFile.uri;
} catch (error) {
console.error('Save photo error:', error);
throw new Error('Failed to save photo');
}
}
static async readFileAsBase64(filePath: string): Promise<string> {
try {
const file = await Filesystem.readFile({
path: filePath
});
return file.data as string;
} catch (error) {
console.error('Read file error:', error);
throw new Error('Failed to read file');
}
}
private static async readAsBase64(photo: CameraPhoto): Promise<string> {
if (photo.webviewPath) {
// Fetch the photo, read as a blob, then convert to base64 format
const response = await fetch(photo.webviewPath);
const blob = await response.blob();
return await this.convertBlobToBase64(blob) as string;
} else {
// For Capacitor on Android/iOS, we can read directly from file system
return await this.readFileAsBase64(photo.filepath);
}
}
private static convertBlobToBase64(blob: Blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onerror = reject;
reader.onload = () => {
resolve(reader.result);
};
reader.readAsDataURL(blob);
});
}
// Share Operations
static async shareContent(
title: string,
text?: string,
url?: string,
dialogTitle?: string
): Promise<void> {
try {
await Share.share({
title,
text,
url,
dialogTitle: dialogTitle || 'Share with buddies'
});
} catch (error) {
console.error('Share error:', error);
throw new Error('Failed to share content');
}
}
}
// src/components/DeviceFeatures.tsx (React Component)
import React, { useState, useEffect } from 'react';
import { DeviceService, DevicePosition, CameraPhoto } from '../services/DeviceService';
const DeviceFeatures: React.FC = () => {
const [position, setPosition] = useState<DevicePosition | null>(null);
const [photo, setPhoto] = useState<CameraPhoto | null>(null);
const [watchId, setWatchId] = useState<string | null>(null);
useEffect(() => {
requestPermissions();
return () => {
if (watchId) {
Geolocation.clearWatch({ id: watchId });
}
};
}, []);
const requestPermissions = async () => {
await DeviceService.requestNotificationPermission();
};
const handleTakePhoto = async () => {
try {
const newPhoto = await DeviceService.takePhoto();
setPhoto(newPhoto);
await DeviceService.savePhotoToFile(newPhoto);
} catch (error) {
console.error('Photo error:', error);
}
};
const handleGetCurrentPosition = async () => {
try {
const currentPosition = await DeviceService.getCurrentPosition();
setPosition(currentPosition);
} catch (error) {
console.error('Position error:', error);
}
};
const handleWatchPosition = async () => {
try {
const id = await DeviceService.watchPosition((pos) => {
setPosition(pos);
});
setWatchId(id);
} catch (error) {
console.error('Watch position error:', error);
}
};
const handleScheduleNotification = async () => {
try {
await DeviceService.scheduleLocalNotification(
'Hello from Capacitor!',
'This is a local notification.',
{ at: new Date(Date.now() + 5000) } // 5 seconds from now
);
} catch (error) {
console.error('Notification error:', error);
}
};
const handleShare = async () => {
try {
await DeviceService.shareContent(
'Check out my location!',
`I'm at lat: ${position?.latitude}, lng: ${position?.longitude}`,
undefined,
'Share Location'
);
} catch (error) {
console.error('Share error:', error);
}
};
return (
<div className="device-features">
<h2>Capacitor Device APIs Demo</h2>
<section>
<h3>Camera</h3>
<button onClick={handleTakePhoto}>Take Photo</button>
{photo && (
<div>
<img src={photo.webviewPath} alt="Taken photo" style={{ maxWidth: '200px' }} />
</div>
)}
</section>
<section>
<h3>Geolocation</h3>
<button onClick={handleGetCurrentPosition}>Get Current Position</button>
<button onClick={handleWatchPosition}>Watch Position</button>
{position && (
<div>
<p>Latitude: {position.latitude}</p>
<p>Longitude: {position.longitude}</p>
<p>Accuracy: {position.accuracy}m</p>
<p>Timestamp: {new Date(position.timestamp).toLocaleString()}</p>
<button onClick={handleShare}>Share Location</button>
</div>
)}
</section>
<section>
<h3>Notifications</h3>
<button onClick={handleScheduleNotification}>
Schedule Notification (5s)
</button>
</section>
</div>
);
};
export default DeviceFeatures;
💻 Capacitor Custom Plugin Development typescript
🟡 intermediate
Create custom Capacitor plugins for platform-specific functionality with Swift (iOS) and Java (Android) implementations
// src/definitions.ts
import { PluginListenerHandle } from '@capacitor/core';
export interface MyCustomPlugin {
/**
* Perform a custom operation
*/
customOperation(options: CustomOperationOptions): Promise<CustomOperationResult>;
/**
* Get device information
*/
getDeviceInfo(): Promise<DeviceInfo>;
/**
* Listen for custom events
*/
addListener(
eventName: 'customEvent',
listenerFunc: (event: CustomEvent) => void
): Promise<PluginListenerHandle> & PluginListenerHandle;
/**
* Remove all listeners for this plugin
*/
removeAllListeners(): Promise<void>;
}
export interface CustomOperationOptions {
value: string;
mode?: 'normal' | 'advanced';
}
export interface CustomOperationResult {
success: boolean;
processedValue: string;
timestamp: number;
}
export interface DeviceInfo {
platform: string;
version: string;
customProperty: string;
}
export interface CustomEvent {
data: any;
timestamp: number;
}
// src/web.ts
import { WebPlugin } from '@capacitor/core';
import { MyCustomPlugin, CustomOperationOptions, CustomOperationResult, DeviceInfo, CustomEvent } from './definitions';
export class MyCustomPluginWeb extends WebPlugin implements MyCustomPlugin {
constructor() {
super();
}
async customOperation(options: CustomOperationOptions): Promise<CustomOperationResult> {
// Web implementation
const processedValue = options.mode === 'advanced'
? `ADVANCED: ${options.value}`
: `NORMAL: ${options.value}`;
return {
success: true,
processedValue,
timestamp: Date.now()
};
}
async getDeviceInfo(): Promise<DeviceInfo> {
return {
platform: 'web',
version: navigator.userAgent,
customProperty: 'web-specific-value'
};
}
async addListener(eventName: 'customEvent', listenerFunc: (event: CustomEvent) => void): Promise<any> {
// Web event listener implementation
window.addEventListener(eventName, (event: any) => {
listenerFunc(event.detail);
});
}
async removeAllListeners(): Promise<void> {
// Web cleanup
window.removeEventListener('customEvent', () => {});
}
}
// src/index.ts
import { registerPlugin } from '@capacitor/core';
import type { MyCustomPlugin } from './definitions';
const MyCustomPlugin = registerPlugin<MyCustomPlugin>('MyCustomPlugin', {
web: () => import('./web').then(m => new m.MyCustomPluginWeb()),
});
export * from './definitions';
export { MyCustomPlugin };
// iOS Plugin (Swift) - ios/Plugin/MyCustomPlugin.swift
import Foundation
import Capacitor
@objc(MyCustomPlugin)
public class MyCustomPlugin: CAPPlugin, CAPPluginMethod {
@objc func customOperation(_ call: CAPPluginCall) {
guard let value = call.getString("value") else {
call.reject("Missing required option: value")
return
}
let mode = call.getString("mode") ?? "normal"
let processedValue = mode == "advanced" ? "ADVANCED: \(value)" : "NORMAL: \(value)"
let result = [
"success": true,
"processedValue": processedValue,
"timestamp": Date().timeIntervalSince1970 * 1000
] as [String : Any]
call.resolve(result)
}
@objc func getDeviceInfo(_ call: CAPPluginCall) {
let device = UIDevice.current
let result = [
"platform": "ios",
"version": device.systemVersion,
"customProperty": "ios-specific-value"
]
call.resolve(result)
}
@objc override public func load() {
// Plugin initialization
NotificationCenter.default.addObserver(
self,
selector: #selector(handleCustomNotification),
name: NSNotification.Name("CustomEvent"),
object: nil
)
}
@objc func handleCustomNotification(_ notification: Notification) {
guard let data = notification.userInfo else { return }
let eventData = [
"data": data,
"timestamp": Date().timeIntervalSince1970 * 1000
] as [String : Any]
notifyListeners("customEvent", data: eventData)
}
}
// iOS Plugin (Objective-C) - ios/Plugin/MyCustomPluginPlugin.m
#import <Foundation/Foundation.h>
#import <Capacitor/Capacitor.h>
// Define the plugin using CAP_PLUGIN Macro
CAP_PLUGIN(MyCustomPlugin, "MyCustomPlugin",
CAP_PLUGIN_METHOD(customOperation, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(getDeviceInfo, CAPPluginReturnPromise);
)
// Android Plugin (Java) - com.example.myapp/MyCustomPlugin.java
package com.example.myapp;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
@CapacitorPlugin(name = "MyCustomPlugin")
public class MyCustomPlugin extends Plugin {
@PluginMethod
public void customOperation(PluginCall call) {
String value = call.getString("value");
if (value == null) {
call.reject("Missing required option: value");
return;
}
String mode = call.getString("mode", "normal");
String processedValue = "advanced".equals(mode)
? "ADVANCED: " + value
: "NORMAL: " + value;
JSObject result = new JSObject();
result.put("success", true);
result.put("processedValue", processedValue);
result.put("timestamp", System.currentTimeMillis());
call.resolve(result);
}
@PluginMethod
public void getDeviceInfo(PluginCall call) {
JSObject result = new JSObject();
result.put("platform", "android");
result.put("version", android.os.Build.VERSION.RELEASE);
result.put("customProperty", "android-specific-value");
call.resolve(result);
}
@Override
public void load() {
// Plugin initialization
}
}
// Usage in your app
import { MyCustomPlugin } from 'capacitor-my-custom-plugin';
class CustomPluginService {
static async performCustomOperation(value: string, mode?: 'normal' | 'advanced') {
try {
const result = await MyCustomPlugin.customOperation({ value, mode });
console.log('Custom operation result:', result);
return result;
} catch (error) {
console.error('Custom operation error:', error);
throw error;
}
}
static async getDeviceInformation() {
try {
const deviceInfo = await MyCustomPlugin.getDeviceInfo();
console.log('Device info:', deviceInfo);
return deviceInfo;
} catch (error) {
console.error('Get device info error:', error);
throw error;
}
}
static async listenForCustomEvents() {
await MyCustomPlugin.addListener('customEvent', (event) => {
console.log('Custom event received:', event);
// Handle the event
});
}
}
💻 Capacitor PWA Deployment and Optimization typescript
🔴 complex
Configure Capacitor app as Progressive Web App with offline capabilities, service workers, and app manifest optimization
// public/manifest.json
{
"name": "Capacitor PWA Demo",
"short_name": "CapacitorPWA",
"description": "A progressive web app built with Capacitor",
"start_url": "/",
"display": "standalone",
"background_color": "#3880ff",
"theme_color": "#3880ff",
"orientation": "portrait",
"icons": [
{
"src": "assets/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "assets/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "assets/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "assets/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "assets/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "assets/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "assets/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "assets/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"splash_pages": null
}
// public/sw.js (Service Worker)
const CACHE_NAME = 'capacitor-pwa-v1';
const STATIC_CACHE = 'static-cache-v1';
const DYNAMIC_CACHE = 'dynamic-cache-v1';
const STATIC_ASSETS = [
'/',
'/index.html',
'/assets/icons/icon-192x192.png',
'/assets/icons/icon-512x512.png',
'/manifest.json'
];
// Install event - cache static assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(STATIC_CACHE)
.then((cache) => {
console.log('Service Worker: Caching static assets');
return cache.addAll(STATIC_ASSETS);
})
.then(() => self.skipWaiting())
);
});
// Activate event - clean up old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys()
.then((cacheNames) => {
return Promise.all(
cacheNames.map((cache) => {
if (cache !== STATIC_CACHE && cache !== DYNAMIC_CACHE) {
console.log('Service Worker: Clearing old cache');
return caches.delete(cache);
}
})
);
})
.then(() => self.clients.claim())
);
});
// Fetch event - implement caching strategies
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
// Cache first strategy for static assets
if (event.request.destination === 'script' ||
event.request.destination === 'style' ||
event.request.destination === 'image') {
return response || fetch(event.request);
}
// Network first strategy for API calls
if (event.request.url.includes('/api/')) {
return fetch(event.request)
.then((fetchResponse) => {
// Cache successful API responses
if (fetchResponse.status === 200) {
caches.open(DYNAMIC_CACHE)
.then((cache) => cache.put(event.request, fetchResponse.clone()));
}
return fetchResponse;
})
.catch(() => {
// Fallback to cached API response if network fails
return caches.match(event.request);
});
}
// Network first for HTML pages
if (event.request.destination === 'document') {
return fetch(event.request)
.then((fetchResponse) => {
// Cache successful page responses
if (fetchResponse.status === 200) {
caches.open(DYNAMIC_CACHE)
.then((cache) => cache.put(event.request, fetchResponse.clone()));
}
return fetchResponse;
})
.catch(() => {
// Fallback to cached page or offline page
return caches.match(event.request) ||
caches.match('/offline.html') ||
new Response('Offline', { status: 503 });
});
}
return fetch(event.request);
})
);
});
// Background sync for offline actions
self.addEventListener('sync', (event) => {
if (event.tag === 'background-sync') {
event.waitUntil(doBackgroundSync());
}
});
async function doBackgroundSync() {
// Handle offline data synchronization
const offlineActions = await getOfflineActions();
for (const action of offlineActions) {
try {
await processOfflineAction(action);
await removeOfflineAction(action.id);
} catch (error) {
console.error('Background sync failed:', error);
}
}
}
// Push notifications
self.addEventListener('push', (event) => {
const options = {
body: event.data.text(),
icon: '/assets/icons/icon-192x192.png',
badge: '/assets/icons/icon-72x72.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: 1
},
actions: [
{
action: 'explore',
title: 'Explore',
icon: '/assets/icons/checkmark.png'
},
{
action: 'close',
title: 'Close',
icon: '/assets/icons/xmark.png'
}
]
};
event.waitUntil(
self.registration.showNotification('Push Notification', options)
);
});
// src/services/PWAService.ts
import { Capacitor } from '@capacitor/core';
export interface OfflineAction {
id: string;
type: string;
data: any;
timestamp: number;
}
export class PWAService {
private static isPWA(): boolean {
return Capacitor.getPlatform() === 'web' &&
(window.matchMedia('(display-mode: standalone)').matches ||
(window.navigator as any).standalone);
}
private static isInStandaloneMode(): boolean {
return ('standalone' in window.navigator && (window.navigator as any).standalone) ||
window.matchMedia('(display-mode: standalone)').matches;
}
static async installPWA(): Promise<void> {
if (!this.isPWA() && 'serviceWorker' in navigator) {
try {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('Service Worker registered:', registration);
// Show install prompt
this.showInstallPrompt();
} catch (error) {
console.error('Service Worker registration failed:', error);
}
}
}
private static deferredPrompt: any;
static showInstallPrompt(): void {
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
this.deferredPrompt = e;
// Show custom install UI
this.showInstallUI();
});
}
private static showInstallUI(): void {
const installButton = document.createElement('button');
installButton.textContent = 'Install App';
installButton.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
padding: 12px 24px;
background: #3880ff;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
z-index: 1000;
font-size: 16px;
`;
installButton.addEventListener('click', async () => {
if (this.deferredPrompt) {
this.deferredPrompt.prompt();
const { outcome } = await this.deferredPrompt.userChoice;
if (outcome === 'accepted') {
console.log('PWA installation accepted');
} else {
console.log('PWA installation dismissed');
}
this.deferredPrompt = null;
installButton.remove();
}
});
document.body.appendChild(installButton);
}
static async saveOfflineAction(action: Omit<OfflineAction, 'id' | 'timestamp'>): Promise<void> {
const offlineAction: OfflineAction = {
...action,
id: `${Date.now()}-${Math.random()}`,
timestamp: Date.now()
};
const existingActions = await this.getOfflineActions();
existingActions.push(offlineAction);
localStorage.setItem('offlineActions', JSON.stringify(existingActions));
// Register background sync if available
if ('serviceWorker' in navigator && 'sync' in window.ServiceWorkerRegistration.prototype) {
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('background-sync');
}
}
static async getOfflineActions(): Promise<OfflineAction[]> {
const stored = localStorage.getItem('offlineActions');
return stored ? JSON.parse(stored) : [];
}
static async removeOfflineAction(id: string): Promise<void> {
const actions = await this.getOfflineActions();
const filtered = actions.filter(action => action.id !== id);
localStorage.setItem('offlineActions', JSON.stringify(filtered));
}
static isOnline(): boolean {
return navigator.onLine;
}
static async checkConnectivity(): Promise<boolean> {
if (!this.isOnline()) {
return false;
}
try {
const response = await fetch('/api/health', {
method: 'HEAD',
cache: 'no-cache'
});
return response.ok;
} catch {
return false;
}
}
static setupConnectivityListeners(callback: (isOnline: boolean) => void): void {
window.addEventListener('online', () => callback(true));
window.addEventListener('offline', () => callback(false));
}
static async getAppVersion(): Promise<string> {
try {
const response = await fetch('/manifest.json');
const manifest = await response.json();
return manifest.version || '1.0.0';
} catch {
return '1.0.0';
}
}
static getDeviceInfo(): { isPWA: boolean; isStandalone: boolean; platform: string } {
return {
isPWA: this.isPWA(),
isStandalone: this.isInStandaloneMode(),
platform: Capacitor.getPlatform()
};
}
}
// src/App.tsx (React Integration)
import React, { useState, useEffect } from 'react';
import { PWAService } from './services/PWAService';
const App: React.FC = () => {
const [isOnline, setIsOnline] = useState(PWAService.isOnline());
const [appInfo, setAppInfo] = useState(PWAService.getDeviceInfo());
useEffect(() => {
// Initialize PWA
PWAService.installPWA();
// Setup connectivity listeners
PWAService.setupConnectivityListeners(setIsOnline);
// Check app version
PWAService.getAppVersion().then(version => {
console.log('App version:', version);
});
// Handle offline actions when coming back online
if (isOnline) {
PWAService.getOfflineActions().then(actions => {
if (actions.length > 0) {
console.log('Processing offline actions:', actions.length);
}
});
}
}, [isOnline]);
return (
<div className="app">
<header>
<h1>Capacitor PWA Demo</h1>
<div className="status">
<span className={isOnline ? 'online' : 'offline'}>
{isOnline ? '🟢 Online' : '🔴 Offline'}
</span>
<span>Platform: {appInfo.platform}</span>
{appInfo.isPWA && <span>PWA Mode</span>}
</div>
</header>
<main>
{/* Your app content */}
</main>
</div>
);
};
export default App;