@codeonair/code-push-react-native
v1.1.1
Published
CodePush SDK for React Native: over-the-air (OTA) JavaScript and asset updates with mandatory releases, staged rollouts and crash rollback. App Center CodePush alternative.
Maintainers
Readme
@codeonair/code-push-react-native
The CodePush SDK for React Native: over-the-air (OTA) JavaScript and asset updates from
CodeOnAir CodePush, with mandatory releases, staged rollouts and automatic rollback of
an update that crashes. An alternative to App Center CodePush and react-native-code-push.
Works with the Old Architecture, the New Architecture (including bridgeless) and Hermes, on React Native 0.70+.
Website · Quick start · Release updates with the CLI: @codeonair/code-push-cli
| Tested on devices (Android emulator + iOS simulator) | Old Architecture | New Architecture | | --- | --- | --- | | React Native 0.86 | — (removed in RN 0.82) | ✅ | | React Native 0.76 | ✅ | ✅ |
Each run covers: optional update on the next start, mandatory update right away, and rollback of an update that crashes at startup.
npm install @codeonair/code-push-react-native
cd ios && pod installConfigure
Dashboard → your app → CodePush → Android / iOS → Set up CodePush. Use your platform's deployment key and the server URL:
<!-- android/app/src/main/res/values/strings.xml -->
<string moduleConfig="true" name="CodePushDeploymentKey">cpd_…</string>
<string moduleConfig="true" name="CodePushServerUrl">https://app.codeonair.com</string><!-- ios/<App>/Info.plist -->
<key>CodePushDeploymentKey</key>
<string>cpd_…</string>
<key>CodePushServerURL</key>
<string>https://app.codeonair.com</string>Load the update's bundle
// MainApplication.kt
import com.codeonair.codepush.CodeOnAirCodePush
getDefaultReactHost(
context = applicationContext,
packageList = PackageList(this).packages,
jsBundleFilePath = CodeOnAirCodePush.getJSBundleFile(applicationContext),
)React Native 0.70–0.76 (DefaultReactNativeHost in MainApplication) — the same override works for the Old and
the New Architecture:
// MainApplication.kt
import com.codeonair.codepush.CodeOnAirCodePush
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
// …getPackages(), getJSMainModuleName() as before…
override fun getJSBundleFile(): String? = CodeOnAirCodePush.getJSBundleFile(applicationContext)
}// AppDelegate.swift
import CodeOnAirCodePush
override func bundleURL() -> URL? {
#if DEBUG
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
CodeOnAirCodePush.bundleURL()
#endif
}React Native 0.70–0.76 (AppDelegate.mm), Old and New Architecture:
#import <CodeOnAirCodePush/CodeOnAirCodePush.h>
- (NSURL *)bundleURL
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [CodeOnAirCodePush bundleURL];
#endif
}Notes:
- React Native 0.76 and older with Xcode 16.3+ fail to compile the
fmtpod (call to consteval function … is not a constant expression). That's React Native, not CodePush: upgrade React Native, or turn off fmt's consteval in your Podfile'spost_install(replacedefine FMT_USE_CONSTEVAL 1with0inPods/fmt/include/fmt/base.h). - iOS 27 stops apps that haven't adopted the UIScene lifecycle at launch; the stock React Native template hasn't yet.
Use
import CodePush from "@codeonair/code-push-react-native";
function App() {
useEffect(() => {
// Every launch: confirms the running update, then checks for a new one.
CodePush.sync(
{
installMode: CodePush.InstallMode.ON_NEXT_RESTART, // optional updates: on the next app start
mandatoryInstallMode: CodePush.InstallMode.IMMEDIATE, // Mandatory builds: right away
},
(status) => {
if (status === CodePush.SyncStatus.UPDATE_INSTALLED) console.log("Update installed.");
},
);
}, []);
// …
}
export default App;Call CodePush.sync() on every launch: it confirms the running update first, so an update that crashes before then is
rolled back on the next launch and reported. installMode applies to optional updates (default ON_NEXT_RESTART) and
mandatoryInstallMode to builds marked Mandatory (default IMMEDIATE); either can be any mode:
| Install mode | When the update runs |
| --- | --- |
| ON_NEXT_RESTART | The next time the app is started. |
| IMMEDIATE | Right away: the app restarts as soon as it's downloaded. |
| ON_NEXT_RESUME | When the app next comes back from the background. |
| ON_NEXT_SUSPEND | While the app is in the background — the user comes back to the new version already running. |
minimumBackgroundDuration (seconds, default 0) makes ON_NEXT_RESUME / ON_NEXT_SUSPEND wait until the app has
been in the background at least that long, so a quick app switch doesn't restart it.
Mandatory updates without a visible reload
The old setup keeps working exactly as before. To never restart in front of the user, install mandatory updates with
ON_NEXT_SUSPEND and bring them back to the same screen:
import codePush from "@codeonair/code-push-react-native";
import { NavigationContainer } from "@react-navigation/native";
function App() {
return (
<NavigationContainer
initialState={codePush.restoredScreenState} // the screen from before a CodePush restart, else undefined
onStateChange={codePush.trackScreenState}>
{/* … */}
</NavigationContainer>
);
}
export default codePush({
mandatoryInstallMode: codePush.InstallMode.ON_NEXT_SUSPEND,
minimumBackgroundDuration: 10,
restoreScreen: true, // loads the saved screen state before the app renders
})(App);Any other state, or without the wrapper: codePush.setRestartStateProvider(() => state) is called just before every
CodePush restart (any JSON), and await codePush.getRestoredState() returns it once after the restart. It's null after a
normal cold start and when the update rolled back. React Navigation isn't a dependency.
How ON_NEXT_SUSPEND restarts: about a second after the app goes to the background (or after
minimumBackgroundDuration), a native timer restarts it — iOS reloads React Native under a background task; Android
points the ReactHost (or ReactInstanceManager) at the new bundle and reloads it in place, or ends the process if that
isn't possible so the next start runs the update. Coming back first cancels it until the next time the app is in the
background. iOS gives a backgrounded app about 30 seconds and Android may freeze it sooner, so keep the wait short; the
update always runs on the next cold start anyway.
export default codePush(App) is an optional wrapper that syncs on start. checkForUpdate(), install(),
notifyAppReady(), restartApp() and getUpdateMetadata() are there for custom flows.
Release updates with the CLI, @codeonair/code-push-cli (npm install -g @codeonair/code-push-cli@beta). Full docs: dashboard.codeonair.com/docs/codepush/overview.
The JavaScript engine (src/core.ts) is tested end to end against the CodeOnAir API, including install modes, restart state and rollback.
