There are two main ways to create a mobile app: cross-platform and native.
There are two main ways to create a mobile app: cross-platform and native.
Generally speaking, with a cross-platform application it’s more difficult to reach the native features of the phone – such as the camera, or Bluetooth capability. Also, the performance of a cross-platform app seldom exceeds the performance of a native app.
On the plus side, the codebase for a cross-platform app is usually smaller, as the code is reused across all the platforms the app runs on. This means fewer engineering hours are needed, which helps to keep development costs under control.
There are multiple frameworks to choose from for building a cross-platform mobile app, including Flutter, Ionic and the one we will be focusing on here: React Native.
You start by visiting the official webpage (in our case: reactnative.dev) and following the instructions for setting up a development environment. At this point we already face our first dilemma: we see that if we are new to mobile development, we are advised to use the Expo CLI instead of the React Native CLI. Expo is a set of tools and services built for React Native, but as with React Native itself, Expo can be thought of as a framework and thus an abstraction layer.
In computer science, abstraction layers are often used to generalize concepts and help us focus on the important details without reinventing the wheel each time we start a new project. But using too many abstraction layers may lead to complex problems. This is because we move further away from the actual implementation of how things work, making it difficult to trace the root of a problem.
The next thing we need are emulators to see how our app looks on phones. These are provided by XCode and Android Studio for iPhone and Android respectively. This is where we notice the first characteristic of having an Expo application. When using the Expo Go app, we can scan a QR code that appears in the browser when we start our app on the computer. This QR code allows us to see how the app we are making looks on our phone.
If we want a scalable app, the final aspect we should take care of within the setup process is automation. The first steps in automating our development process are common in web or desktop applications (e.g. code in git repos, pipelines with automated testing, etc.). The next two steps are more mobile-specific, including creating an artifact and distributing it through a mobile store. As these two steps are very platform-specific – they differentiate between iOS and Android – people often use mobile CI/CD services that have better integrations for mobile. The cool thing with Expo is that it has its own CI/CD services deeply integrated for Expo apps.
(Image: Example of a development process for mobile apps)
By creating two template projects – one with React Native CLI and one with Expo CLI – we can make an initial comparison between the project without Expo and the project with Expo. The directories generated are shown in the figure below.
(Image: React Native without Expo (left), React Native with Expo (right))
The first thing one notices is that the Expo template project does not contain Android and iOS folders as a bare React Native project does. That immediately means there are fewer configurations to take care of when one adds a package or creates a build.
But then how do we reach the native components, such as the camera? For this Expo contains built-in modules that one can use without writing native code. This solution is not complete though, as not all native components are supported. That’s why one can eject from an Expo project to a bare React Native project and regain access to native code.
One of the drawbacks of Expo is that the app size of the build is larger, as it includes some SDKs that one might not need. In addition, not all 3rd party libraries are supported and not all native APIs are available.
To get a sense of the benefit of Expo, let’s take a look at a bare React Native project. Specifically, we’ll look at adding a package that contains native bridges.
For this example, we can use the DateTime picker package, as many applications need it to be able to choose the date or time. It contains native bridges to the iOS spinner or Android clock. There are many different published versions of this package, so we can choose the one that best suits our needs. We follow the instructions to install it with npm or yarn, but then we must ensure that Gradle and CocoaPods are also synchronized or linked.
In its latest versions, React Native has taken steps to support autolinking and to reduce the manual work one must do. Some manual work is still required though, which can be problematic. More and more packages are added in the project and many developers are working in different branches, thus it’s very likely that the synchronization between the package managers will break. The usual fixes for this are:
These steps will often solve the problem and restore the synchronization, but they are time consuming. Mobile development has higher compiling and initialization times for emulators, for builds, and for runtime. Android Studio and XCode are powerful software packages, but they run heavy. If one must configure both on a regular basis in the same project, it could lead to long waiting times and high maintenance costs.
The above process seems loosely related to a story from Airbnb a few years ago. The team had native versions of their application – integrated with React Native – which did not go as expected, so they switched back to native applications.
Although numerous technical and organizational issues are listed, the dominant issues seem to have been the following: as native components were initially used for their application, they integrated React Native and tried to wrap the native components within it (an option one gets only in a bare React Native project, without Expo). This eventually increased complexity and maintenance time until it was no longer worth it. Having native apps seemed like a better choice. Although it is possible to include native components with a bare React Native project, it may not be optimal in the long run.
It’s recommended to take the time to plan the project properly and aim to get it right at the first attempt. It helps to list all the native features needed for the app, and see if there are packages that support them in React Native and in Expo. Then the team should be sized. For example, if the team consists of native developers, it could make more sense to go for a native app. If we are willing to go for a cross-platform app, we should investigate if React Native suits the case better than the alternatives.
If all of the above check out and we are going for React Native, it’s recommended to use Expo. Although it’s another abstraction layer – and one should be careful with them – it seems to enhance many steps of the development process, so it’s probably safe to make an exception.
Konstantinos held a lightning talk about this topic on JavaZone 2022. If you missed it you can see his entire speech at Javazone here.
Konstantinos is a software consultant who works with creating web and mobile applications. He enjoys finding simple solutions to complex problems and learning on a daily basis. He endorses having a sharing culture within the company and beyond, as he believes inclusion helps discovering out of the box solutions. Apart from sitting between chair and keyboard, he may be found browsing vinyls in record stores.