A React Native application comprises bundled JavaScript files and associated images, packaged by the metro bundler into platform-specific binaries (e.g., .ipa or .apk files)
. CodePush is designed to deliver product improvements to end users by synchronizing JavaScript and images with updates released to the CodePush server.
Given the slow performance of the Microsoft CodePush cloud in China and Iran, we have the opportunity to establish our own solution. The library I’m using has opted for Qiniu
to store files due to its simplicity and rapid functionality. Alternatively, you can choose [local/s3/oss/Tencent Cloud]
storage and patch the necessary adjustments in the config.js
file.
Setting up a custom Code-Push Server
You can check the complete repository along with complete installation guide here; You can install it both by docker and manual installation; for simplicity, I’ve added the docker implementation here:
Clone Repo
git clone https://github.com/shm-open/code-push-server.git
cd code-push-server
Modify configuration file
vim docker-compose.yml
Replace YOUR_MACHINE_IP in DOWNLOAD_URL with your own external network ip or domain name.
Deploying Docker
docker-compose up -d
Access interface simple verification
curl -I http://YOUR_CODE_PUSH_SERVER_IP:3000/
the response must be sth like this:
HTTP/1.1 200 OK
X-DNS-Prefetch-Control: off
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Content-Type: text/html; charset=utf-8
Content-Length: 592
Date: Sat, 23 Nov 2023 15:45:46 GMT
Connection: keep-alive
Configuring React-Native App for Code-Push
The complete guide along with more information about all the features and how to implement them can be found here;
Note: In order to ensure that your end users always have a functioning version of your app, the CodePush plugin maintains a copy of the previous update, so that in the event that you accidentally push an update which includes a crash, it can automatically roll back. This way, you can rest assured that your newfound release agility won’t result in users becoming blocked before you have a chance to roll back on the server. This is one of the subjects that I’m going to talk about in Part 2 :).
Installing react-native-code-push
npm install --save react-native-code-push
Similar to other React Native plugins, the integration process varies for iOS and Android. Therefore, follow the setup steps below based on the platform(s) you are focusing on. It’s important to note that when targeting both platforms, it is advisable to create distinct CodePush applications for each.
This article assumes you’re using RN > 0.6 version; If you’re using an older version or want to find out more about how to setup on each platform, kindly check out the links provided in the following sections:
Configuring on ios
Complete setup guide is provided here;
- Run the following command in the terminal to install all necessary CocoaPods dependencies:
cd ios && pod install && cd ..
2. Open the AppDelegate.m
file and add the following import statement for CodePush:
#import <CodePush/CodePush.h>
3. Locate the line of code setting the source URL for the bridge for production releases:
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
4. Replace it with the following line to configure your app to always load the most recent version of the JS bundle:
return [CodePush bundleURL];
5. Update your sourceURLForBridge
method as follows:
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
return [CodePush bundleURL];
#endif
}
6. Add the Deployment key to Info.plist
:
Open your app's Info.plist
file and add a new entry named CodePushDeploymentKey
, with its value as the key of the deployment you want to configure. Retrieve this value using the Code-Push CLI (I'm going to explain in the next-step; :) ):
<key>CodePushDeploymentKey</key>
<string>DEPLOYMENT_KEY</string>
<key>CodePushServerURL</key>
<string>CODE_PUSH_SERVER_URL</string>
Configuring on ios
Complete setup guide is provided here;
- Update
android/settings.gradle
In yourandroid/settings.gradle
file, add the following lines at the end:
...
include ':app', ':react-native-code-push'
project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')
...
2. Modify android/app/build.gradle
In your android/app/build.gradle
file, include the codepush.gradle
file as an additional build task definition at the end:
...
apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"
...
3. Update MainApplication.java
Make changes to the MainApplication.java
file to incorporate CodePush:
...
// 1. Import the plugin class.
import com.microsoft.codepush.react.CodePush;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
...
// 2. Override the getJSBundleFile method to let
// the CodePush runtime determine the JS
// bundle location on each app start
@Override
protected String getJSBundleFile() {
return CodePush.getJSBundleFile();
}
};
}
4. Add Deployment Key to strings.xml:
To inform the CodePush runtime about the deployment to query for updates, update your app's strings.xml
file. Add a new string named CodePushDeploymentKey
with the deployment key you want to configure. Retrieve this value using the CodePush CLI:
<resources>
<string name="app_name">AppName</string>
<string moduleConfig="true" name="CodePushDeploymentKey">DEPLOYMENT_KEY</string>
<string moduleConfig="true" name="CodePushServerUrl">CODE_PUSH_SERVER_URL</string>
</resources>
Configuring on App.tsx
Now that you’ve integrated the CodePush plugin into your app and established communication with CodePush to fetch the appropriate JS bundle, the remaining task is to implement the necessary code in your app to govern the following policies:
- When (and how often) to check for an update (e.g., app start, in response to a button click, periodically at fixed intervals).
- How to present an available update to the end user.
The straightforward approach is to “CodePush-ify” your app’s root component, and you can choose between two options:
Option 1: Wrap your root component with the codePush higher-order component: For class component:
import codePush from "react-native-code-push";
class MyApp extends Component {
}
MyApp = codePush(MyApp);
For functional component:
import codePush from "react-native-code-push";
let MyApp: () => React$Node = () => {
}
MyApp = codePush(MyApp);
Option 2: Use the ES7 decorator syntax: For class component:
import codePush from "react-native-code-push";
@codePush
class MyApp extends Component {
}
For functional component:
import codePush from "react-native-code-push";
const MyApp: () => React$Node = () => {
}
export default codePush(MyApp);
By default, CodePush checks for updates on every app start. If an update is available, it is silently downloaded and installed upon the next app restart, ensuring a minimally disruptive experience for end users. Mandatory updates are installed immediately to ensure users receive them promptly. To expedite update discovery, you can sync with the CodePush server each time the app resumes from the background:
For class component:
let codePushOptions = { checkFrequency: codePush.CheckFrequency.ON_APP_RESUME };
class MyApp extends Component {
}
MyApp = codePush(codePushOptions)(MyApp);
For functional component:
let codePushOptions = { checkFrequency: codePush.CheckFrequency.ON_APP_RESUME };
let MyApp: () => React$Node = () => {
}
MyApp = codePush(codePushOptions)(MyApp);
Deploying Updates
Now is the time to push changes to the deployment server! The tool I’ve used serves as a divergence from the original open-source code-push-server. The decision to create this fork stems from the deprecation of the original code-push-cli and the incompatibility of appcenter-cli with self-hosted code-push-server instances.
Install
npm install -g @shm-open/code-push-cli
Register / Login using your AppCenter / Code-Push Account
code-push login YOUR_DEPLOYMENT_SERVER_URL
Managing App
Before you can deploy any updates, you need to register an app with the CodePush service using the following command:
code-push app add <appName> <os> <platform>
If your app targets both iOS and Android, please create separate apps for each platform with CodePush; so that you can manage and release updates to them separately, which in the long run, also tends to make things simpler. For example:
Android:
code-push app add MyApp-Android android react-native
ios:
code-push app add MyApp-iOS ios react-native
This will give you two default deployment environments, (Production, Staging) [In order to find out how to use both environments in the app, stay tuned for part 2 :)]
Bundling App
Now, in order to create the code-push update of the app, we bundle our application using the following command: [p.s: first, create a folder named code-push to store bundle files and assets]
Android:
npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output ./code-push/index.android.bundle --assets-dest ./code-push
ios:
npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output ./code-push/main.jsbundle --assets-dest ./code-push
Notice the index.android.bundle name on Android and main.jsbundle on ios.
Releasing Bundle to the Deployment Server
Once your app has been configured to query for updates against the CodePush server, you can begin releasing updates to it.
Android:
code-push release MyApp-Android ./code-push/index.android.bundle "*"
ios:
code-push release MyApp-ios ./code-push/main.jsbundle "*"
Now, If everything is implemented properly, you can see that by opening the application, the app would ask for an update from your code-push server and update the bundle on next restart. 🎉
What I’m going to talk about in Part 2
- Rollback
- Multi-environment Deployments
- Automating Deployment Process
- Code-Signing on Platforms
Edited: Part two available here
Conclusion
This was my first React-Native tutorial :); I just want to say a big thank you for reading this writeup! Putting this guide together has been a fantastic learning journey. If you have any thoughts, comments, or suggestions to make it even better, I’m all ears. I know there might be a hiccup or two — sorry about that! Your feedback is super important, and I can’t wait to use it for future tutorials. Thanks a bunch for being part of this experience. Happy coding, and see you in the next one! 🚀