The following article is a part of The Ultimate Guide to React Native Optimization and describes how to improve your apps through Continuous Integration (CI). It’s been co-authored by Expo, who shared their insights on setting up EAS Build and running your build on it.
Why is it important?
A lack of working Continuous Integration may seriously harm your apps on many different levels. It can slow down the development process and testing, and expose your final app to bugs and regressions that may decrease your income. In this article, I'll show you how to bulletproof your React Native app for such unpleasant situations with Continuous Integration.
In the previous parts of our guide, we have discussed:
- Over-The-Air (OTA) updates
- Shipping Fast with Continuous Deployment
- Making your app consistently fast with DMAIC and Reassure
- Running tests for key pieces of your app
- Profiling React Native apps with iOS and Android tools
Check them out later, and now let's jump into the main topic.
Many faces of feedback loop in app development
As you have already learned from our other article in this series, covering your code with tests can be very helpful for increasing the overall reliability of your app. However, while testing your product is vital, it is not the only prerequisite on your way to shipping faster and with more confidence.
Equally important is how quickly you detect the potential regressions and whether finding them is a part of your daily development lifecycle. In other words – it all comes down to the feedback loop.
How long feedback loop (and no CI) can harm your React Native project
For better context, let’s take a look at the early days of the development process. When you’re starting out, your focus is on shipping the first iteration (MVP) as fast as possible. Because of that, you may overlook the importance of the architecture itself. When you’re done with the changes, you submit them to the repository, letting other members of your team know that the feature is ready to be reviewed.
While this technique can be very useful, it is potentially dangerous on its own, especially as your team grows in size. Before you’re ready to accept a PR, you should not only analyze the code, but also clone it to your environment and test it thoroughly. At the very end of that process, it may turn out that the proposed changes introduce a regression that the original author hasn’t spotted.
The reason for that is simple - we all have different configurations, environments, and ways of working.
If you’re testing your changes manually, you’re not only increasing the chances of shipping regressions to production. You’re also slowing down the overall pace of the development. Thankfully, with the right set of methodologies and a bit of automation, you can overcome this challenge once and for all.
What is Continuous Integration and how does it work?
This is when Continuous Integration (CI) comes into play. CI is a development practice where proposed changes are checked-in to the upstream repository several times a day by the development team. Next, they are verified by an automated build, allowing the team to detect changes early.
The automated builds are performed by a dedicated cloud-based CI provider that usually integrates with the place where you store your code. Most cloud providers available these days support Github, a Microsoft-owned platform for collaborating on projects that use git as their version control system.
CI systems pull the changes in real-time and perform a selected set of tests, to give you early feedback on your results. This approach introduces a single source of truth for testing and allows developers with different environments to receive convenient and reliable information.
Using a CI service, you can not only test your code but also build a new version of documentation for your project, build your app, and distribute it among testers or releases. This technique is called Continuous Deployment and focuses on the automation of releases.
<p-bg-col>Following the Circle CI section, you'll find a section on EAS Build in this chapter. Why? We believe having a subsection about EAS Build also helps developers achieve results as with CircleCI.<p-bg-col>
Using a Continuous Integration provider to build your React Native app
There are a lot of CI providers to choose from, with the most popular being CircleCI, GitHub Actions, and Bitrise. For React Native applications, another option is EAS Build, a service created by Expo with first-class support for React Native.
We have selected CircleCI as our reference CI provider for the purpose of this section, as it has broad community adoption. In fact, there is an example project demonstrating the use of CI with React Native. You can learn more about it here. We will employ it later in this section to present different CI concepts.
We will also dive into EAS Build, which is designed to make building and testing applications as easy as possible. It also helps with distributing app binaries to the stores and internally amongst your team.
<p-bg-col>Note: A good practice is to take advantage of what React Native / React Native Community projects already use. Going that route, you can ensure that it is possible to make your chosen provider work with React Native and that the most common challenges have been already solved by the Core Team.<p-bg-col>
How to set up CI with CircleCI
With most of the CI providers, it is extremely important to study their configuration files before you do anything else.
Let’s take a look at a sample configuration file for CircleCI, taken from the mentioned React Native example:
The structure is a standard Yaml syntax for text-based configuration files. You may want to learn about its basics before proceeding any further.
<p-bg-col>Note: Many CI services, such as CircleCI or Github Actions, are based on Docker containers and the idea of composing different jobs into workflows. Github and its Github Actions is an example of such provider. You may find many similarities between those services.<p-bg-col>
Commands, jobs, and workflows in CircleCI configuration
There are three most important building blocks of a CircleCI configuration: <rte-code>commands<rte-code>, <rte-code> jobs<rte-code>, and <rte-code>workflows<rte-code>.
<rte-code>command<rte-code> is nothing more but a shell script. It is executed within the specified environment. Also, it is what performs the actual job in the cloud. It can be anything, from a simple command to install your dependencies, such as <rte-code>yarn install<rte-code> (if you’re using Yarn) to a bit more complex one <rte-code>./gradlew assembleDebug <rte-code> that builds Android files.
<rte-code>job<rte-code> is a series of commands - described as steps - that is focused on achieving a single, defined goal. <rte-code>jobs<rte-code> can be run in different environments, by choosing an appropriate Docker container.
For example, you may want to use a Node container if you need to run only your React unit tests. As a result, the container will be smaller, have fewer dependencies, and will install faster. If you want to build a React Native application in the cloud, you may choose a different container, e.g. with Android NDK/SDK or the one that uses OS X to build Apple platforms.
To help you choose the container to use when running React Native tests, the team has prepared react-native-android Docker container that includes both Node and Android dependencies needed to perform the Android build and tests.
In order to execute a <rte-code>job<rte-code>, it has to be assigned to a <rte-code>workflow<rte-code>. By default, <rte-code>jobs<rte-code> will be executed parallelly within a workflow, but this can be changed by specifying requirements for a <rte-code>job<rte-code>.
You can also modify the job execution schedule by adding filters, so for instance a deploy job will run only if the changes in the code refer to a master branch.
You can define many workflows for different purposes, e.g. one for tests that would run once a PR is opened, and the other to deploy the new version of the app. This is what React Native does to automatically release its new versions every once in a while.
Setting up EAS Build
EAS stands for Expo Application Services. One of the services it provides is EAS Build. In contrast with a generic CI provider, it is designed for building and testing React Native applications, handles app-signing credentials and internal distribution amongst your team members, and also closely integrates with EAS Submit to automate app store submissions.
EAS Build is a managed service. This means many workflow steps that are traditionally manually defined in a generic CI provider are handled automatically. It also uses a new build environment for each build job with the tools such as the Java JDK, the Android SDK and NDK, Xcode, Fastlane, etc. already installed.
You can get started by following just a few steps below on your development computer. After these steps, you will have set up your project for EAS, configured your build profile, and started a build job.
Running an EAS Build
First, install the <rte-code>eas-cli<rte-code> package by running this command in your terminal:
Or, you can use <rte-code>npx eas-cli<rte-code> to run the commands if you’d like to avoid installing a global dependency.
Setting up EAS Build
Next, initialize a configuration file for EAS Build and run this command in the root directory of your project:
This command will create a file named <rte-code>eas.json<rte-code> in the root directory of your project. It contains the whole config for EAS to run properly.
Your project may need some additional configuration for cases like using a monorepo. Read this guide to see how to configure your build profile further.
<rte-code>eas.json<rte-code> is a text-based configuration file that uses standard JSON syntax. Inside, you will find a top-level field named <rte-code>build<rte-code>. This field contains a JSON object with all of the configuration that defines your app’s build profiles. A <rte-code>build profile<rte-code> is a named group of configuration options that describe the necessary parameters to create specific types of builds, like for internal distribution or an app store release.
The JSON object under <rte-code>build<rte-code> contains multiple profiles. By default, the <rte-code>development<rte-code>, <rte-code>preview<rte-code>, and <rte-code>production<rte-code> are the three build profiles provided. The <rte-code>development profile<rte-code> is for debug builds that include developer tools and is mainly used during development and testing. The <rte-code>preview<rte-code> profile doesn’t include developer tools and is for builds intended to be shared with your team when testing your app in a production-like environment. Finally, the production profile is for release builds that are submitted to app stores.
You can define additional custom profiles under the build field for specific types of builds to best fit your project and team’s needs. Additionally, each profile can also have platform-specific configuration for Android and iOS.
Running your build on EAS Build
Any type of build can be triggered from a single command. For example, you can use <rte-code>preview<rte-code> profile to share the app with your team for testing:
The <rte-code>--platform all<rte-code> option lets you build for Android and iOS at the same time.
A build profile that has its <rte-code>distribution<rte-code> field set to <rte-code>“internal”<rte-code> (as shown in the example <rte-code>eas.json<rte-code> configuration earlier) configures EAS Build to provide shareable URLs. Once the build is complete, this URL can be shared with your teammates for internal distribution. By using the URL, any member of your team can download the app to their device.
You can modify a build profile anytime during the development lifecycle of your project. In some cases, additional configuration may be required or useful, like when working on a project inside a monorepo or sharing configuration between different profiles.
With CI, you get early feedback on added features, swiftly spot the regressions, and save time
A properly configured and working CI provider can save you a lot of time when shipping a new version of an application.
By spotting errors beforehand, you can reduce the effort needed to review the PRs and protect your product against regressions and bugs that may directly decrease your income.
Interested in improving your React Native app?
We are the official Facebook partners on React Native. We’ve been working on React Native projects for over 5 years, delivering high-quality solutions for our clients and contributing greatly to the React Native ecosystem. Our Open Source projects help thousands of developers to cope with their challenges and make their work easier every day. Our React Native development company offers a wide range of services.