Over-the-air code push updates for Flutter apps. Push updates to your users without going through the app store review process.
This package provides the runtime SDK that you add to your Flutter app.
It communicates with a code-push-enabled Flutter engine (installed via the
fcp CLI) to check for updates, download patches, apply them, and
automatically roll back if something goes wrong.
- Automatic update checking -- checks on launch, periodically, and on app resume
- Live patching (iOS) -- bytecode modules load without restarting the app
- Restart-based patching (Android/Desktop) -- ELF patches applied on next cold restart
- Crash protection with auto-rollback -- reverts bad patches after repeated failed boots
- RSA signature verification -- optional cryptographic signing for patch integrity
- SHA-256 hash verification -- always-on integrity check for every patch
- Debug status bar -- opt-in overlay showing real-time code push status
fcpCLI installed (dart pub global activate flutter_compile)- A FlutterPlaza Code Push account -- run
fcp codepush login - Code-push-enabled engine artifacts -- run
fcp codepush setup
dependencies:
flutterplaza_code_push: ^0.1.0flutter pub getWrap your root widget with CodePushOverlay. This handles the entire update
lifecycle automatically -- checking for updates, downloading patches, and
showing a restart banner when an update is ready.
import 'package:flutterplaza_code_push/flutterplaza_code_push.dart';
void main() {
runApp(
CodePushOverlay(
config: CodePushConfig(
serverUrl: 'https://your-server.com',
appId: 'your-app-id',
releaseVersion: '1.0.0+1',
),
child: MyApp(),
),
);
}That is the only change needed. The overlay checks for updates on launch, every 4 hours (configurable), and whenever the app returns from the background. When a patch is downloaded and installed, a banner appears prompting the user to restart.
During development, you can enable a small status bar at the top of the screen that shows what code push is doing in real time:
CodePushOverlay(
config: CodePushConfig(
serverUrl: 'https://your-server.com',
appId: 'your-app-id',
releaseVersion: '1.0.0+1',
),
showDebugBar: true, // Shows "CP: Checking server...", "CP: Patch active", etc.
child: MyApp(),
)Starts the automatic update lifecycle. Call once at app startup. This is what
CodePushOverlay calls internally -- you only need this if you are not
using the overlay widget.
CodePush.init(
serverUrl: 'https://your-server.com',
appId: 'your-app-id',
releaseVersion: '1.0.0+1',
interval: Duration(hours: 4), // optional, default 4 hours
channel: 'production', // optional, default 'production'
onUpdateReady: () {
// Called when a patch is installed and a restart is needed.
},
);What init does:
- Runs crash protection checks (auto-rollback if needed)
- Checks for updates immediately
- Checks periodically at the configured interval
- Reports launch success after a 10-second grace period
Stops automatic update checking and cancels the launch timer.
CodePush.dispose();Checks the server for updates, downloads, and installs if available. Returns
true if a patch was installed.
final installed = await CodePush.checkAndInstall(
serverUrl: 'https://your-server.com',
appId: 'your-app-id',
releaseVersion: '1.0.0+1',
channel: 'production',
onUpdateReady: () {
// Prompt user to restart (Android/Desktop only).
},
);On iOS, bytecode patches are loaded live without a restart. On Android and
desktop, onUpdateReady is called so you can prompt the user to restart.
Checks the engine for available updates without downloading.
final UpdateInfo info = await CodePush.checkForUpdate();
if (info.isUpdateAvailable) {
print('Patch ${info.patchVersion} available (${info.downloadSize} bytes)');
}Installs a patch from raw bytes. Use this when you download the patch yourself (for example, via your own HTTP client).
final Uint8List patchBytes = await myHttpClient.downloadPatch(url);
await CodePush.installPatch(patchBytes);The engine verifies patch integrity (SHA-256 hash, optional RSA signature) before installing. The patch takes effect on the next cold restart.
Rolls back to the base release by removing the active patch. Takes effect on next cold restart.
await CodePush.rollback();Triggers a cold restart of the app. On next launch, the engine loads the installed patch.
CodePush.restart();Returns whether the app is currently running with a code push patch.
final bool patched = await CodePush.isPatched;Returns information about the currently installed patch, or null if none
is active.
final PatchInfo? patch = await CodePush.currentPatch;
if (patch != null) {
print('Version: ${patch.version}');
print('Installed at: ${patch.installedAt}');
}Returns the release version string for this app build.
final String version = await CodePush.releaseVersion;A ValueNotifier<String> that broadcasts what code push is currently doing.
Useful for debug UIs or logging.
CodePush.status.addListener(() {
print('Code push status: ${CodePush.status.value}');
});Values include: init, Checking server..., Downloading patch...,
Patch active, No update (204), Restart to apply, etc.
A ValueNotifier<Object?> that holds the result from the last loaded
bytecode module (iOS live patches). Apps can listen to this to apply OTA
patches to their UI without a restart.
CodePush.moduleResult.addListener(() {
final result = CodePush.moduleResult.value;
if (result is Map<String, dynamic>) {
// Use the patch data to update your UI.
}
});The recommended way to integrate code push. Wraps your app widget, manages the full update lifecycle, and shows a restart banner when an update is ready.
CodePushOverlay(
config: CodePushConfig(...),
child: MyApp(),
showDebugBar: false, // optional, shows status bar at top
bannerBuilder: (context, onRestart, onDismiss) {
// optional, return a custom banner widget
return MyCustomBanner(onRestart: onRestart, onDismiss: onDismiss);
},
)Configuration object for CodePushOverlay.
| Parameter | Type | Default | Description |
|---|---|---|---|
serverUrl |
String |
required | Your code push server URL |
appId |
String |
required | Your app's identifier |
releaseVersion |
String |
required | The current release version (e.g. 1.0.0+1) |
checkInterval |
Duration |
4 hours | How often to check for updates |
channel |
String |
production |
The update channel |
A widget that rebuilds when a bytecode module result becomes available. Use this to apply OTA patches to specific parts of your UI.
CodePushPatchBuilder(
patchKey: 'promo_banner',
builder: (context, patchData, child) {
if (patchData == null) return child!;
return Text(patchData);
},
child: Text('Default content'),
)If patchKey is provided, the builder only receives data from module results
that start with that key (e.g. promo_banner:Hello World passes
Hello World to the builder). If patchKey is null, all module results are
passed through.
| Field | Type | Description |
|---|---|---|
isUpdateAvailable |
bool |
Whether an update is available |
patchVersion |
String? |
Version string of the available patch |
downloadSize |
int? |
Size of the patch in bytes |
| Field | Type | Description |
|---|---|---|
version |
String |
Patch version string |
installedAt |
DateTime |
When the patch was installed |
Thrown when a code push operation fails. Contains a message field describing
the error.
Code push includes automatic crash protection to prevent a bad patch from bricking your app. Here is how it works:
- Boot counter -- Each time the app starts with an active patch, a boot counter is incremented.
- Grace period -- After 10 seconds of successful execution, the launch is marked as successful and the boot counter resets to zero.
- Auto-rollback -- If the app fails to survive the grace period 3 times in a row, the patch is automatically removed on the next launch, reverting the app to its base release.
This works on all platforms:
- Android and Desktop -- The engine handles crash protection natively in C++.
- iOS -- Crash protection runs in Dart (the engine's native updater is disabled on iOS due to Apple Clang LTO constraints).
No configuration is needed. Crash protection is always active when a patch is installed.
Every patch is verified before installation:
The engine computes a SHA-256 hash of every downloaded patch and verifies it against the expected hash. Tampered or corrupted patches are rejected.
For additional security, you can configure RSA signature verification. When enabled, the engine verifies that each patch was signed with your private key before installing it.
iOS -- Add your RSA public key to Info.plist:
<key>FLTCodePushPublicKey</key>
<string>-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhki...your key here...
-----END PUBLIC KEY-----</string>Android -- Add your RSA public key to codepush.yaml in your project root:
public_key: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhki...your key here...
-----END PUBLIC KEY-----When a public key is configured, patches without a valid signature are rejected. When no public key is configured, signature verification is skipped (SHA-256 hash verification still applies).
| Platform | Patch Type | Restart Required | Live Reload |
|---|---|---|---|
| iOS | Bytecode | No (data modules) | Yes |
| Android | ELF | Yes | No |
| Desktop | ELF | Yes | No |
- iOS: Bytecode patches are loaded as data modules at runtime. The app does
not need to restart. Listen to
CodePush.moduleResultor useCodePushPatchBuilderto react to live patches. - Android and Desktop: ELF patches are written to disk and loaded by the
engine on the next cold restart. The
onUpdateReadycallback (or the overlay banner) lets you prompt the user to restart.
1. fcp codepush login # one-time auth
2. fcp codepush setup # download engine artifacts
3. fcp codepush init # register app on server
4. fcp codepush release --build --platform apk # upload baseline
5. # ... make code changes ...
6. fcp codepush patch --build --platform apk --release-id <id> # upload patch
7. App detects update, downloads, installs, and restarts (or live-loads on iOS)
BSD 3-Clause. See LICENSE for details.