Implementing OTA Updates
1. Setting Up CodePush
| Property | Description | Values |
|---|---|---|
| appVersion | Current version of the app | "1.0.0" |
| deploymentKey | Deployment key for the CodePush server | "your-deployment-key" |
| serverURL | URL of the CodePush server | "https://codepush.appcenter.ms" |
Example: Initializing CodePush
import codePush from "react-native-codepush";
let codePushOptions = {
appVersion: "1.0.0",
deploymentKey: "your-deployment-key",
serverURL: "https://codepush.appcenter.ms"
};
codePush.sync(codePushOptions);
2. Implementing Silent Updates
| Property | Description | Default | Notes |
|---|---|---|---|
| updateUrl | URL to fetch the update manifest | None | Required for silent updates |
| checkFrequency | How often to check for updates (in minutes) | 60 | Set to 0 to disable automatic checks |
| autoUpdate | Whether to apply updates silently | false | Set to true for silent updates |
| updateMode | Update behavior: 'immediate', 'onNextResume', or 'onNextRestart' | 'immediate' | Controls when updates are applied |
Example: Silent Update Configuration
const config = {
updateUrl: 'https://myapp.com/updates/latest',
autoUpdate: true,
checkFrequency: 30,
updateMode: 'immediate'
};
// Apply silent updates
const updateManager = new UpdateManager(config);
updateManager.start();
Note: Silent updates require the app to be built with the
react-native-code-push or similar library.
Warning: Silent updates can cause app instability if not properly tested. Always validate updates in a staging environment before deploying to production.
3. Handling Mandatory Updates
| Property | Description | Default | Notes |
|---|---|---|---|
| mandatory | Indicates whether the update must be installed immediately | false |
When set to true, the app will not allow users to skip the update |
| updateUrl | URL to download the update package | null |
Required if mandatory is true |
| version | Version of the update | null |
Used for tracking and logging purposes |
Example: Mandatory Update Configuration
{
"mandatory": true,
"updateUrl": "https://example.com/updates/app-update-v2.0.zip",
"version": "2.0.0"
}
Note: Mandatory updates should be used sparingly to avoid frustrating users. Ensure that the update includes critical security fixes or essential functionality improvements before marking it as mandatory.
Warning: If an update is marked as mandatory, the app will block access to the main UI until the update is installed. Make sure that users are informed about the necessity of the update and its impact on their experience.
4. Using Expo Updates
Example: Configuring Expo Updates
{
"expo": {
"updates": {
"url": "https://exp.host/@your-app-username/your-app-name"
},
"assetBundlePatterns": [
"**/*"
]
}
}
| Property | Description | Default | Notes |
|---|---|---|---|
| url | URL to the update manifest | None | Required for OTA updates |
| enabled | Whether to enable OTA updates | true |
Can be disabled for testing |
| checkAutomatically | How often to check for updates | ON_LOAD |
Options: ON_LOAD, ON_ERROR, NEVER |
Note: Updates are only available for apps published to Expo Go or a standalone app built with
expo build.
Example: Checking for Updates
import * as Updates from 'expo-updates';
async function checkForUpdate() {
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}
} catch (error) {
console.error(error);
}
}
Update Flow
- User opens app
- App checks for update at configured URL
- If update exists, downloads and applies it
- App reloads with new code
5. Managing Update Rollbacks
| Property | Description | Default | Notes |
|---|---|---|---|
| rollback | Enables or disables automatic rollback on failed updates | false |
Set to true to enable automatic rollback |
| maxRollbackAttempts | Maximum number of rollback attempts before giving up | 3 |
Used to prevent infinite rollback loops |
| rollbackTimeout | Time (in ms) to wait before attempting a rollback | 5000 |
Allows time for app to stabilize before rollback |
Example: Configuring Rollback Behavior
const config = {
rollback: true,
maxRollbackAttempts: 3,
rollbackTimeout: 5000
};
// Apply to OTA update manager
updateManager.setConfig(config);
Note: Rollback behavior is handled by the OTA update manager and must be configured before updates are applied.
Rollback Process Overview
- Update fails during installation or runtime
- OTA manager checks
rollbackflag - If enabled, attempts to restore previous stable version
- Tracks
maxRollbackAttemptsto prevent infinite loops - Uses
rollbackTimeoutto ensure app stability before rollback
Manual Rollback Workflow
- Identify failed update version
- Fetch previous stable version from server
- Install and validate previous version
- Update local state to reflect rollback
- Notify user of successful rollback
6. Testing OTA Updates
Example: Testing OTA Update Flow
import { Platform } from 'react-native';
import { checkForUpdateAsync, fetchUpdateAsync, reloadAsync } from 'expo-updates';
const testOTAUpdate = async () => {
try {
// Check for available update
const update = await checkForUpdateAsync();
if (update.isAvailable) {
console.log('Update available:', update);
// Fetch the update
await fetchUpdateAsync();
// Reload to apply update (only works in standalone app)
if (Platform.OS !== 'web') {
await reloadAsync();
}
} else {
console.log('No update available');
}
} catch (error) {
console.error('OTA update test failed:', error);
}
};
testOTAUpdate();
| Method | Description | Platform Support | Notes |
|---|---|---|---|
| checkForUpdateAsync() | Checks if an update is available | iOS / Android | Returns update info or null if no update |
| fetchUpdateAsync() | Downloads the update package | iOS / Android | Must be called before reload |
| reloadAsync() | Applies and reloads the app with new update | iOS / Android | Only works in standalone apps, not dev mode |
Note: OTA updates only work in standalone apps (built with EAS Build or Expo Go). In development mode, updates are not applied.
Warning: Ensure all tests are run in a production-like environment to simulate real user conditions.
Testing OTA Update Workflow
- Build and deploy an update to a test channel (e.g.,
test) - Ensure the app is configured to check for updates on launch
- Run the app in a standalone build (not Expo Go)
- Verify that
checkForUpdateAsync()detects the new version - Call
fetchUpdateAsync()andreloadAsync() - Confirm the app reloads with updated code
7. Implementing Update UI
Example: Basic Update UI Component
import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
const UpdateUI = ({ onUpdate, onCancel }) => {
const [progress, setProgress] = useState(0);
return (
<View style={styles.container}>
<Text style={styles.title}>Update Available</Text>
<Text style={styles.message}>A new version is ready to install.</Text>
<View style={styles.progressBar}>
<View
style={[styles.progressFill, { width: `${progress}%` }]}
/>
</View>
<Text style={styles.progressText}>{progress}%</Text>
<View style={styles.buttonContainer}>
<Button title="Install Now" onPress={onUpdate} />
<Button title="Later" onPress={onCancel} color="#ccc" />
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#fff',
borderRadius: 10,
alignItems: 'center',
},
title: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 10,
},
message: {
fontSize: 14,
marginBottom: 20,
textAlign: 'center',
},
progressBar: {
width: '100%',
height: 10,
backgroundColor: '#eee',
borderRadius: 5,
marginBottom: 10,
},
progressFill: {
height: '100%',
backgroundColor: '#4CAF50',
borderRadius: 5,
},
progressText: {
fontSize: 12,
color: '#666',
marginBottom: 20,
},
buttonContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
width: '100%',
},
});
export default UpdateUI;
| Property | Description | Default Value |
|---|---|---|
| onUpdate | Callback function to initiate the update installation | null |
| onCancel | Callback function to cancel the update | null |
| progress | Current progress of the update in percentage (0-100) | 0 |
Note: The progress bar should be updated in real-time during the update process to provide feedback to the user.
Warning: Ensure that the update UI is displayed only when an update is actually available to avoid misleading users.
8. Configuring Update Policies
| Policy | Description | Default Value | Notes |
|---|---|---|---|
| updatePolicy | Defines when and how updates are applied | Background |
Can be Background, Immediate, or Manual |
| checkFrequency | How often to check for updates | EveryLaunch |
Options: EveryLaunch, Weekly, Monthly |
| minBackgroundFetchInterval | Minimum time between background fetches (in seconds) | 30 |
Only applies to Background policy |
| autoUpdate | Whether to automatically download and apply updates | true |
Set to false for manual control |
Example: Configuring Update Policies in Code
const config = {
updatePolicy: 'Background',
checkFrequency: 'EveryLaunch',
minBackgroundFetchInterval: 30,
autoUpdate: true
};
// Apply config to your OTA update client
Note: Policies can be overridden at runtime or via remote configuration.
9. Monitoring Update Success Rates
Example: Basic Success Rate Tracking
import { AppState } from 'react-native';
const updateSuccessTracker = {
totalUpdates: 0,
successfulUpdates: 0,
failedUpdates: 0,
lastUpdateStatus: null,
};
const trackUpdateSuccess = (status) => {
updateSuccessTracker.totalUpdates += 1;
updateSuccessTracker.lastUpdateStatus = status;
if (status === 'success') {
updateSuccessTracker.successfulUpdates += 1;
} else if (status === 'failure') {
updateSuccessTracker.failedUpdates += 1;
}
};
// Example usage with a hypothetical OTA update library
const handleUpdate = async () => {
try {
const result = await checkForUpdates();
if (result.updateAvailable) {
const updateResult = await downloadAndInstallUpdate();
trackUpdateSuccess(updateResult ? 'success' : 'failure');
}
} catch (error) {
trackUpdateSuccess('failure');
}
};
| Metric | Description | Formula | Threshold |
|---|---|---|---|
| Success Rate | Percentage of successful OTA updates | (successfulUpdates / totalUpdates) * 100 |
≥ 95% |
| Failure Rate | Percentage of failed OTA updates | (failedUpdates / totalUpdates) * 100 |
≤ 5% |
| Update Latency | Average time taken to download and install update | avg(downloadTime + installTime) |
< 30s |
Note: Success rate tracking should be implemented at both the client and server level to ensure accurate reporting.
Warning: If the success rate drops below 95%, consider implementing rollback mechanisms or alerting system administrators.
Key Metrics to Track
- Success Rate: Measures the reliability of OTA updates.
- Failure Rate: Identifies potential issues with update distribution.
- Update Latency: Helps optimize the update delivery process.
10. Handling Update Errors
| Error Type | Description | Handling Strategy | Recovery Options |
|---|---|---|---|
| Network Timeout | Update download fails due to network issues. | Retry with exponential backoff or notify user. | Retry, fallback to cached version, or manual update. |
| Download Failure | Update file corrupted or incomplete. | Validate checksums before installation. | Re-download, restore from previous version. |
| Installation Failure | Update fails to install due to system constraints. | Log error and revert to previous version. | Manual update, system reboot, or rollback. |
| Signature Mismatch | Update does not match expected signature. | Reject update and report error to server. | Retry with valid update, notify admin. |
Example: Error Handling with Expo Updates
import { updateAsync, shouldRollBack } from 'expo-updates';
async function handleUpdateError(error) {
console.error('Update failed:', error);
if (shouldRollBack()) {
await updateAsync(); // Rollback to previous version
}
}
Note: Always log update errors for debugging and monitoring purposes.
Warning: Never allow a failed update to crash the app without proper error recovery.