capacitor-native-update
Version:
Native Update Plugin for Capacitor
786 lines (649 loc) ⢠18.7 kB
Markdown
The App Updates feature provides a seamless way to manage native app store updates, ensuring users always have the latest version of your app with critical native changes, new permissions, or platform-specific features.
While Live Updates handle web assets, App Updates manage the native app binary itself. This feature integrates with:
- **Google Play Store** on Android (using Play Core Library)
- **Apple App Store** on iOS (using manual version checking)
- **Web platforms** with fallback notifications
## Key Features
### šÆ Update Priority Management
- Configure update urgency (0-5 scale)
- Force critical updates
- Flexible update scheduling
### š± Native UI Integration
- Platform-specific update dialogs
- In-app download progress
- Seamless installation flow
### š Update Types
- **Immediate Updates**: Blocking updates for critical fixes
- **Flexible Updates**: Background downloads with user control
- **Manual Updates**: Direct app store navigation
### š Version Intelligence
- Semantic version comparison
- Minimum version enforcement
- Update availability detection
## Implementation Guide
### Basic Implementation
```typescript
// Check for app updates on startup
async function checkForAppUpdates() {
try {
const updateInfo = await CapacitorNativeUpdate.AppUpdate.getAppUpdateInfo();
if (updateInfo.updateAvailable) {
console.log(`Update available: ${updateInfo.availableVersion}`);
// Handle based on priority
if (updateInfo.updatePriority >= 4) {
// High priority - immediate update
await CapacitorNativeUpdate.AppUpdate.performImmediateUpdate();
} else {
// Optional update - show custom UI
showUpdateDialog(updateInfo);
}
}
} catch (error) {
console.error('Update check failed:', error);
}
}
```
```typescript
class AppUpdateManager {
private updateInfo: AppUpdateInfo | null = null;
private downloadProgress = 0;
async initialize() {
// Configure app updates
await CapacitorNativeUpdate.configure({
appUpdate: {
checkOnAppStart: true,
minimumVersion: '2.0.0',
updatePriority: 3,
storeUrl: {
android: 'https://play.google.com/store/apps/details?id=com.myapp',
ios: 'https://apps.apple.com/app/id123456789',
},
},
});
// Set up listeners
this.setupUpdateListeners();
// Check for updates
await this.checkForUpdates();
}
private setupUpdateListeners() {
// Android flexible update state changes
CapacitorNativeUpdate.AppUpdate.addListener(
'flexibleUpdateStateChanged',
(state) => {
this.handleFlexibleUpdateState(state);
}
);
// Download progress for flexible updates
CapacitorNativeUpdate.AppUpdate.addListener(
'flexibleUpdateProgress',
(progress) => {
this.downloadProgress = progress.percent;
this.updateProgressUI(progress);
}
);
}
async checkForUpdates() {
try {
this.updateInfo =
await CapacitorNativeUpdate.AppUpdate.getAppUpdateInfo();
if (!this.updateInfo.updateAvailable) {
console.log('App is up to date');
return;
}
// Analyze update type needed
const updateType = this.determineUpdateType(this.updateInfo);
switch (updateType) {
case 'IMMEDIATE':
await this.performImmediateUpdate();
break;
case 'FLEXIBLE':
await this.startFlexibleUpdate();
break;
case 'OPTIONAL':
this.showOptionalUpdateUI();
break;
}
} catch (error) {
this.handleUpdateError(error);
}
}
private determineUpdateType(info: AppUpdateInfo): string {
// Force immediate update for critical priority
if (info.updatePriority === 5) {
return 'IMMEDIATE';
}
// Check if current version is below minimum
if (this.isVersionBelowMinimum(info.currentVersion, info.minimumVersion)) {
return 'IMMEDIATE';
}
// High priority gets flexible update
if (info.updatePriority >= 3) {
return 'FLEXIBLE';
}
// Low priority is optional
return 'OPTIONAL';
}
async performImmediateUpdate() {
try {
// Show blocking update UI
this.showImmediateUpdateUI();
// Start immediate update
await CapacitorNativeUpdate.AppUpdate.performImmediateUpdate();
// App will restart automatically after update
} catch (error) {
if (error.code === 'UPDATE_CANCELLED') {
// User cancelled - handle based on requirements
if (this.updateInfo?.updatePriority === 5) {
// Critical update - exit app
App.exitApp();
}
}
}
}
async startFlexibleUpdate() {
try {
// Start background download
await CapacitorNativeUpdate.AppUpdate.startFlexibleUpdate();
// Show download progress UI
this.showFlexibleUpdateUI();
} catch (error) {
this.handleUpdateError(error);
}
}
private handleFlexibleUpdateState(state: FlexibleUpdateState) {
switch (state.status) {
case 'PENDING':
console.log('Update pending');
break;
case 'DOWNLOADING':
console.log('Downloading update...');
break;
case 'DOWNLOADED':
// Update ready to install
this.showInstallPrompt();
break;
case 'INSTALLING':
console.log('Installing update...');
break;
case 'INSTALLED':
console.log('Update installed successfully');
break;
case 'FAILED':
this.handleUpdateError(new Error(state.error));
break;
}
}
async completeFlexibleUpdate() {
try {
await CapacitorNativeUpdate.AppUpdate.completeFlexibleUpdate();
// App will restart with new version
} catch (error) {
console.error('Failed to complete update:', error);
}
}
private showInstallPrompt() {
// Show snackbar or dialog
this.showDialog({
title: 'Update Ready',
message: 'An update has been downloaded. Restart to apply?',
buttons: [
{
text: 'Restart',
handler: () => this.completeFlexibleUpdate(),
},
{
text: 'Later',
handler: () => {
// Schedule reminder
this.scheduleUpdateReminder();
},
},
],
});
}
}
```
Android uses the Google Play Core Library for in-app updates:
```typescript
// Android-specific features
const androidUpdate = {
// Immediate update flow
immediate: async () => {
// Blocks UI until update is installed
await CapacitorNativeUpdate.AppUpdate.performImmediateUpdate();
// App restarts automatically
},
// Flexible update flow
flexible: async () => {
// Download in background
await CapacitorNativeUpdate.AppUpdate.startFlexibleUpdate();
// Monitor progress
const listener = await CapacitorNativeUpdate.AppUpdate.addListener(
'flexibleUpdateProgress',
(progress) => {
console.log(
`Downloaded: ${progress.bytesDownloaded}/${progress.totalBytes}`
);
}
);
// Complete when ready
await CapacitorNativeUpdate.AppUpdate.completeFlexibleUpdate();
},
};
```
iOS requires manual version checking and App Store redirection:
```typescript
// iOS-specific implementation
const iosUpdate = {
// Check version manually
check: async () => {
const info = await CapacitorNativeUpdate.AppUpdate.getAppUpdateInfo();
if (info.updateAvailable) {
// Show custom update dialog
const shouldUpdate = await showUpdateDialog(info);
if (shouldUpdate) {
// Open App Store
await CapacitorNativeUpdate.AppUpdate.openAppStore();
}
}
},
};
```
Web platforms show update notifications:
```typescript
// Web fallback
const webUpdate = {
notify: async () => {
const info = await CapacitorNativeUpdate.AppUpdate.getAppUpdateInfo();
if (info.updateAvailable) {
// Show notification
new Notification('Update Available', {
body: `Version ${info.availableVersion} is available`,
icon: '/icon.png',
actions: [
{
action: 'update',
title: 'Update Now',
},
],
});
}
},
};
```
Configure update behavior based on priority:
```typescript
const updatePriorities = {
0: {
// Low priority
name: 'Optional',
behavior: 'Show notification only',
userControl: 'full',
},
1: {
// Minor improvements
name: 'Recommended',
behavior: 'Show dialog on app start',
userControl: 'dismissible',
},
2: {
// Important features
name: 'Important',
behavior: 'Prompt after key actions',
userControl: 'postponable',
},
3: {
// Significant changes
name: 'High',
behavior: 'Flexible update with reminders',
userControl: 'limited',
},
4: {
// Critical fixes
name: 'Critical',
behavior: 'Immediate update prompt',
userControl: 'minimal',
},
5: {
// Security updates
name: 'Mandatory',
behavior: 'Force immediate update',
userControl: 'none',
},
};
```
```typescript
class VersionManager {
// Compare semantic versions
compareVersions(v1: string, v2: string): number {
const parts1 = v1.split('.').map(Number);
const parts2 = v2.split('.').map(Number);
for (let i = 0; i < 3; i++) {
if (parts1[i] > parts2[i]) return 1;
if (parts1[i] < parts2[i]) return -1;
}
return 0;
}
// Check if update is major/minor/patch
getUpdateType(oldVersion: string, newVersion: string): string {
const [oldMajor, oldMinor, oldPatch] = oldVersion.split('.').map(Number);
const [newMajor, newMinor, newPatch] = newVersion.split('.').map(Number);
if (newMajor > oldMajor) return 'major';
if (newMinor > oldMinor) return 'minor';
if (newPatch > oldPatch) return 'patch';
return 'none';
}
// Determine update strategy based on version change
async determineStrategy(info: AppUpdateInfo) {
const updateType = this.getUpdateType(
info.currentVersion,
info.availableVersion
);
switch (updateType) {
case 'major':
// Major updates might need immediate update
return info.updatePriority >= 3 ? 'immediate' : 'flexible';
case 'minor':
// Minor updates are usually flexible
return 'flexible';
case 'patch':
// Patches can be optional unless critical
return info.updatePriority >= 4 ? 'flexible' : 'optional';
default:
return 'none';
}
}
}
```
```typescript
// Enforce minimum version requirements
async function enforceMinimumVersion() {
const config = {
appUpdate: {
minimumVersion: '2.0.0',
forceUpdateBelow: '1.5.0', // Force update for very old versions
},
};
const info = await CapacitorNativeUpdate.AppUpdate.getAppUpdateInfo();
// Check if current version is below minimum
if (isVersionBelow(info.currentVersion, config.appUpdate.minimumVersion)) {
// Force immediate update
try {
await CapacitorNativeUpdate.AppUpdate.performImmediateUpdate();
} catch (error) {
// If update fails or is cancelled, restrict app usage
showMinimumVersionRequired();
disableAppFeatures();
}
}
}
```
```typescript
// Custom update dialog
class UpdateUI {
showUpdateDialog(info: AppUpdateInfo) {
const dialog = {
title: this.getUpdateTitle(info),
message: this.getUpdateMessage(info),
buttons: this.getUpdateButtons(info),
};
return this.presentDialog(dialog);
}
private getUpdateTitle(info: AppUpdateInfo): string {
switch (info.updatePriority) {
case 5:
return 'šØ Critical Update Required';
case 4:
return 'ā ļø Important Update Available';
case 3:
return 'šÆ Recommended Update';
default:
return '⨠New Version Available';
}
}
private getUpdateMessage(info: AppUpdateInfo): string {
const size = this.formatBytes(info.updateSize);
const features = info.releaseNotes?.slice(0, 3).join('\n⢠');
return `Version ${info.availableVersion} is available (${size})
What's new:
⢠${features}
${this.getUpdateUrgency(info)}`;
}
private getUpdateButtons(info: AppUpdateInfo): DialogButton[] {
if (info.updatePriority === 5) {
// Mandatory update - no cancel option
return [
{
text: 'Update Now',
handler: () => this.startUpdate(info),
},
];
}
return [
{
text: 'Update',
handler: () => this.startUpdate(info),
},
{
text: 'Later',
role: 'cancel',
handler: () => this.scheduleReminder(info),
},
];
}
}
```
```typescript
// Update progress UI
class UpdateProgressUI {
private progressBar: HTMLElement;
private statusText: HTMLElement;
showProgress() {
this.createProgressUI();
CapacitorNativeUpdate.AppUpdate.addListener(
'flexibleUpdateProgress',
(progress) => {
this.updateProgress(progress);
}
);
}
private updateProgress(progress: UpdateProgress) {
// Update progress bar
this.progressBar.style.width = `${progress.percent}%`;
// Update status text
const downloaded = this.formatBytes(progress.bytesDownloaded);
const total = this.formatBytes(progress.totalBytes);
this.statusText.textContent = `Downloading: ${downloaded} / ${total}`;
// Show completion
if (progress.percent === 100) {
this.showCompletionUI();
}
}
private showCompletionUI() {
this.statusText.textContent = 'Update ready to install';
this.showInstallButton();
}
}
```
```typescript
async function handleAppUpdateErrors() {
try {
await CapacitorNativeUpdate.AppUpdate.performImmediateUpdate();
} catch (error) {
switch (error.code) {
case 'UPDATE_NOT_AVAILABLE':
console.log('No update available');
break;
case 'UPDATE_CANCELLED':
// User cancelled the update
if (isCriticalUpdate()) {
showMandatoryUpdateMessage();
}
break;
case 'UPDATE_FAILED':
// Update failed to install
showRetryDialog();
break;
case 'PLATFORM_NOT_SUPPORTED':
// Feature not available on this platform
fallbackToManualUpdate();
break;
case 'PLAY_STORE_NOT_FOUND':
// Google Play Store not installed
showAlternativeUpdateMethod();
break;
case 'NETWORK_ERROR':
// No internet connection
showOfflineMessage();
scheduleRetryWhenOnline();
break;
}
}
}
```
```typescript
class UpdateRetryStrategy {
private retryCount = 0;
private maxRetries = 3;
async retryUpdate() {
try {
await CapacitorNativeUpdate.AppUpdate.startFlexibleUpdate();
this.retryCount = 0;
} catch (error) {
if (this.shouldRetry(error)) {
this.retryCount++;
const delay = this.calculateBackoff();
setTimeout(() => {
this.retryUpdate();
}, delay);
} else {
// Give up and show manual update option
this.showManualUpdateOption();
}
}
}
private calculateBackoff(): number {
// Exponential backoff: 2s, 4s, 8s
return Math.pow(2, this.retryCount) * 1000;
}
}
```
```typescript
// Mock update info for testing
const mockUpdateInfo: AppUpdateInfo = {
updateAvailable: true,
currentVersion: '1.0.0',
availableVersion: '2.0.0',
updatePriority: 4,
updateSize: 15 * 1024 * 1024, // 15MB
releaseNotes: [
'New feature: Dark mode',
'Performance improvements',
'Bug fixes',
],
isFlexibleUpdateAllowed: true,
isImmediateUpdateAllowed: true,
};
// Test different scenarios
async function testUpdateScenarios() {
// Test immediate update
await testImmediateUpdate();
// Test flexible update
await testFlexibleUpdate();
// Test update cancellation
await testUpdateCancellation();
// Test network errors
await testNetworkErrors();
}
```
```bash
```
```typescript
// Provide clear update information
const updateMessage = {
title: 'Exciting Update!',
features: [
'šØ Beautiful new design',
'ā” 2x faster performance',
'š Bug fixes and improvements',
],
size: '12 MB',
time: 'Less than 1 minute',
};
```
```typescript
// Update at appropriate times
class SmartUpdateScheduler {
async scheduleUpdate(info: AppUpdateInfo) {
// Don't interrupt critical user flows
if (this.isUserInCriticalFlow()) {
this.scheduleForLater();
return;
}
// Check device conditions
const conditions = await this.checkDeviceConditions();
if (conditions.isCharging && conditions.onWifi) {
// Ideal conditions - start update
await this.startUpdate();
} else {
// Wait for better conditions
this.waitForBetterConditions();
}
}
}
```
```typescript
// Track update metrics
async function trackUpdateMetrics(event: string, data: any) {
await analytics.track({
event: `app_update_${event}`,
properties: {
currentVersion: data.currentVersion,
availableVersion: data.availableVersion,
updatePriority: data.updatePriority,
userAction: data.userAction,
timestamp: Date.now(),
},
});
}
```
- Configure [App Reviews](./app-reviews.md) for user feedback
- Implement [Security Best Practices](../guides/security-best-practices.md)
- Set up [Update Monitoring](../guides/monitoring.md)
- Review [API Reference](../api/app-update-api.md)
---
Made with ā¤ļø by Ahsan Mahmood