Skip to content

Getting started

This guide takes a React Native app from zero to receiving its first OTA update.

For a quick try, run it locally (uses a local D1/R2 simulation):

Terminal window
git clone https://github.com/guhcostan-dev/react-native-airdrop && cd react-native-airdrop
pnpm install
echo 'ADMIN_TOKEN=change-me' > apps/server/.dev.vars
pnpm dev:server # http://localhost:8787

For production, follow Deploy on Cloudflare.

Terminal window
npm install -g react-native-airdrop-cli # or: pnpm --filter react-native-airdrop-cli build && node packages/cli/dist/index.js
airdrop login --server https://ota.example.com --token <ADMIN_TOKEN>

From your React Native project root:

Terminal window
airdrop init --name "My App" --sign

This creates the app on the server, writes airdrop.config.json (safe to commit) and, with --sign, a private key airdrop-private-key.pem (never commit it; store it as a CI secret). It prints the native configuration values used below.

Terminal window
npm install react-native-airdrop
cd ios && pod install

ios/<App>/Info.plist:

<key>AirdropServerURL</key><string>https://ota.example.com</string>
<key>AirdropAppId</key><string>YOUR-APP-ID</string>
<!-- only if you used --sign -->
<key>AirdropPublicKey</key><string>BASE64-PUBLIC-KEY</string>

AppDelegate.swift (RN ≥ 0.77):

import ReactNativeAirdrop
class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
override func bundleURL() -> URL? {
#if DEBUG
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
RNAirdrop.bundleURL()
#endif
}
}

Objective-C AppDelegate.mm (RN 0.71 – 0.76). Use whichever method your template already has (bundleURL, getBundleURL or sourceURLForBridge:) and return [RNAirdrop bundleURL] in release builds:

#import <ReactNativeAirdrop/RNAirdrop.h>
- (NSURL *)bundleURL
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [RNAirdrop bundleURL];
#endif
}

android/app/src/main/res/values/strings.xml:

<string name="airdrop_server_url" translatable="false">https://ota.example.com</string>
<string name="airdrop_app_id" translatable="false">YOUR-APP-ID</string>
<!-- only if you used --sign -->
<string name="airdrop_public_key" translatable="false">BASE64-PUBLIC-KEY</string>

MainApplication.kt (RN ≥ 0.80, getDefaultReactHost):

import com.rnairdrop.Airdrop
override val reactHost: ReactHost by lazy {
getDefaultReactHost(
context = applicationContext,
packageList = PackageList(this).packages,
jsBundleFilePath = Airdrop.getJSBundleFile(applicationContext),
)
}

With a ReactNativeHost (RN 0.71 – 0.79, old or new architecture):

override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
// ...
override fun getJSBundleFile(): String? = Airdrop.getJSBundleFile(applicationContext)
}

AirdropPackage is autolinked.

Wrap your root component:

import { withAirdrop } from "react-native-airdrop";
function App() { /* ... */ }
export default withAirdrop(App);

By default the SDK checks on app start, downloads in the background and applies the update on the next cold start. See SDK reference for other modes.

OTA updates are disabled in debug builds (they load from Metro). Build a release app and install it, then change some JS and run:

Terminal window
airdrop release --platform ios --message "Fix checkout button" --private-key airdrop-private-key.pem
airdrop release --platform android --message "Fix checkout button" --private-key airdrop-private-key.pem

Open the app (it downloads the update), close it and open it again: the new JS is running.

Terminal window
airdrop updates list # history, installs and rollbacks
airdrop release -p android --rollout 10 # staged rollout
airdrop rollout <updateId> 100 # widen it
airdrop rollback <updateId> # pull a bad update; devices fall back automatically
airdrop promote <updateId> --to production