Mastering Over-the-Air Updates in React Native with CodePush: Part 1

UPDATE YOUR APP AT THE SPEED OF LIGHT

Mastering Over-the-Air Updates in React Native with CodePush: Part 1

This post was originally published on Medium by me on Aug 4, 2020

The initial days of a new mobile app development is a melting pot of frequently added features and improvement, broken-for-certain-devices releases, bugs slipped from the tired eyes, and an out-of-patience-midnight-oil-burning-DevOps with a dark sense of humor. The only thing you do not want at this point is a third party authority with their impractical review policies guarding the doors of the mobile phones where your broken code is pushing the users to the brink of uninstalling the app and writing a scathing review on the store about how poorly developed the app is, ignoring all the hard work done by a team of three night owls surviving on Ramen noodle providing the app for free by paying the bills using AWS free credits (https://aws.amazon.com/activate/) and digging into their parents savings. No, we do not want this.

WE WANT SEAMLESS UPDATES, AND WE WANT IT NOW!

Enter CodePush

CodePush is a nifty little utility for React Native and Cordova developers to update JavaScript code and bundle assets on client devices on the fly, in minutes after the release. Remember, Not the full app, just the JavaScript and bundle assets. Let me explain this quickly.

CodePush: push your code

☝️ CodePush: push your code

React Native application is made of JavaScript code, image assets, and platform specific binaries bundled by the bundler. CodePush takes responsibility of keeping the JavaScript code and the asset part of the bundle in sync with the latest code that you have uploaded to the CodePush server. When the user installs your app, they get the version of JavaScript and assets that came with the binary that you uploaded to the AppStore or PlayStore. As soon as the user launches the app, the CodePush client that’s installed as a part of your app, checks the latest version of JavaScript bundle on CodePush server and downloads it. Then on, every time you update the bundle on CodePush server, the app syncs itself with it. Doesn’t it sound too similar to how web pages work?

CodePush code synchronization in short ☝️ CodePush code synchronization in short

Let’s clear up our expectations here. This is what we want:

  1. Set up an AppCenter account and configure our app with the CodePush library
  2. Release the binary (.aab, .apk, .ipa) to the stores of your choice (or just distribute them over FTP, I don’t care)
  3. Change some JavaScript, and release it via CodePush
  4. Restart the app (this time new bundle should be downloaded and installed), and restart again (this time the new bundle should be applied) you should see the new changes
  5. Allow the users to choose whether they want to become beta user
  6. Use CodePush deployments to serve bleeding edge updates to beta users while others still have the stable version
  7. Use CodePush to promote the beta version to production

Setting Up CodePush

First, we need to create a Microsoft AppCenter account and set up a new project. It is easier than setting up your social media account. It is free, so, if you haven’t yet, go ahead and create an account here: https://appcenter.ms/ . I will be waiting for you. Once in, go to Add New > Add New App. Give a recognizable App Name, select OS either Android or iOS, select React Native as Platform and click Add New App button.

Select the App, from the left menu under Distribute, click CodePush. From the center, click "Create Standard Deployments"

CodePush is hidden under Distribute menu

☝️ CodePush is hidden under Distribute menu

Now, you should see a “Everything is Ready” message with info on how to integrate CodePush with your app and how to release it. On the top right corner, you should be seeing a spanner🔧wrench icon. Click it. You should be seeing two identifiers: one titled “Staging” and another named “Production”. Two channels essentially. Two streams if you will. The Names do not matter. You can use any of these for production release. But, no one likes to watch the world burning. So, let’s assume, we will use the Production key for production deployments and Staging for staging release (or beta testers).

What’s in a name: deployment names and corresponding keys

☝️ What’s in a name: deployment names and corresponding keys

For the remaining of the article, whenever we are talking about Deployment Keys, Keys, Production Deployment Key, Staging Deployment Key, or Beta Deployment key, we are talking about one or both of these values. (Over the course of the article, I created two different projects in CodePush, so, the values in the text below may not match the values in the image above)


Integrate CodePush with the App

With our CodePush server configuration done, we are ready to do what we love the most. Code. (Really?) CodePush comes under Microsoft’s AppCenter service which provides much wider services than just managing release synchronizations.

Install AppCenter CLI

Just follow the command and replace appropriate values wherever it makes sense.

# Get the CLI  
npm install -g appcenter-cli

# Login  
appcenter login

# View your apps, we will use these names later  
appcenter apps list

# View the environments and corresponding deployment keys  
# appcenter codepush deployment list -k --app <USERNAME>/<APPNAME>  
appcenter codepush deployment list -k --app nishant/blog-app

┌────────────┬───────────────────────────────────────┐  
│ Name       │ Key                                   │  
├────────────┼───────────────────────────────────────┤  
│ Production │ Jbach1Qk_achQarrrhNOA9zwVlJnruLsa9jsr │  
├────────────┼───────────────────────────────────────┤  
│ Staging    │ jhai-muzz27Nt7Y11KLp-cBsNA22EGjGHEYfr │  
└────────────┴───────────────────────────────────────┘

Code Changes

Install the appropriate version of react-native-code-push as mentioned here: https://github.com/microsoft/react-native-code-push

As of now here is the versions of react-native-code-push and corresponding react-native versions:

WRAP THE APP

Wrap the main app component with CodePush’s higher order component. You can do it the way it is explained here in their official documentation: https://github.com/microsoft/react-native-code-push#plugin-usage

I personally do not like too many things in my App.tsx or App.jsx file. In a realistic case, you must already be wrapping the main App component with various Context providers like Theme, Redux, GraphQL client, Authorization provider et-cetera. Adding more noise to this is just… well, not so pretty. But prettiness apart, CodePush has a separate concern altogether. CodePush is a continuous delivery solution provider, and the rest of the code is either to rendering the UI or to perform the business logic.

So, I created a separate class component that wraps the App component with CodePush component. And, edited index.js to use this new class component. Here is what they look like

☝️ AppWrappedInCodePush.tsx

💡 No CodePush in local env?!

If you don’t wanna activate CodePush while you are in the debugging mode. You can conditionally apply CodePush wrapper like this:

export default __DEV__ ? App : codePush(AppWrappedInCodePush);

☝️ Changes to be made in index.js

Now, we need to do OS specific changes. Please note that OS specific instructions below are for React Native 0.60.0 and onwards. For older versions, refer the official documentation.

Android

For the Android side of the code, here are diffs and list of files to be changed.

☝️ android/app/build.gradle

☝️ MainApplication.java

☝️ strings.xml contains the Production CodePush Deployment Key, use your own key!

☝️ settings.gradle

🐞 POSSIBLE BUG!
Got a message in late 2021, someone saying CodePush sync wasn’t working on Android. Back then, I tested on a new project, it worked. Recently (early 2022), a freelance project I was working on I saw the issue. A little bit of searching around led me to this GitHub post: CodePushUnknownException. In case you are facing this, please make the following change to your android/app/build.gradle file, add the following lines under android.defaultConfig block:

// The code below is one single line!  
resValue 'string', "CODE_PUSH_APK_BUILD_TIME", String.format("\\"%d\\"", System.currentTimeMillis())

iOS

iOS side requires fewer changes, here they are:

☝️ AppDelegate.m

You need to add the CodePushDeploymentKey in your Info.plist and set its value to the Production key from the CodePush console.

You can use XCode to add this, as show below:

Add CodePush Production Deployment Key as a new attribute to Plist, use your own

☝️ Add CodePush Production Deployment Key as a new attribute to Plist, use your own

Or you can edit the Info.plist directly, it looks like this:

☝️ Editing Info.plist in a text editor? Here are the changes. Make sure the Production Deployment Key is right.


Phew!!! A lot of changes, feeling anxious? I think that’s the right response. Now, you need to build the production release binary and distribute it to the users (via AppStore, PlayStore, and/or by just sharing the APK or IPA file).

Cool. The users have the app up and running. Now, make a change (or many changes) to JavaScript files. Perform a CodePush release, here how you do it:

# Make your changes and then  
# appcenter codepush release-react -a <USERNAME>/<APPNAME>  -d Production  
appcenter codepush release-react -a nishant/blog-app -d Production

Detecting android app version:  
Using the target binary version value "0.0.4" from "android/app/build.gradle".

Running "react-native bundle" command:  
[-- snip --]  
index.js --platform android  
Welcome to React Native!  
Learn once, write anywhere  
[-- snip --]

Releasing update contents to CodePush:  
Successfully released an update containing the "/var/folders/89/s5j46yyd7xn1s9ctl\_5td2d80000gn/T/code-push202072-97439-5f5fka.gokh2/CodePush" directory to the "Production" deployment of the "blog-app" app.

Give it a few moments, and then open the app (maybe keep it up for a minute or two so that the sync completes), close the app, and open the app again. You should be seeing the new changes.

Congratulate yourself. 🎉

At this point, we have the pipeline set. Since the majority of the code movement in React Native applications happen in JavaScript, whenever we want to release the updated code to our users, we’d just push the code through CodePush and have it reflected immediately on the users devices without waiting for AppStore or PlayStore to approve it.

A lot of us would be happy with just this. However, wouldn’t it be better if we could have two release streams — one for a smaller audience, the beta testers who would get the preview version and another the stable release for everyone else? When we are happy with the beta version, we’d promote it to stable release for everyone else to see it.

Shouldn’t we have more control over the updates? Is there a way to manually kick off the update? Should we ask the user whether they want the update? Can we show update progress? Can we rollback a bad release? Since you mentioned preview and stable versions, can we use this for A/B testing? Can we have more than two deployment keys? Can we remotely control the user’s version (deployment key) by setting key specific to them on their user profile when they log in? Can we use these keys to switch the environment as in, one key point to test environment APIs and other to production APIs? Also, when is CodePush not enough and we require a binary release to the stores?

We will discuss all these fun things in the next part.

Fresh from the oven, here is the second part