Building iOS and Android apps with React Native

Over the years, we at Appticles have advocated for the use of Progressive Web Apps as an alternative to native apps. PWAs can easily cover most use cases, including providing offline support and web push notifications – Safari has also recently included support for service workers and will continue to improve on it.

However, in some cases, a native app is better suited for the job, like for example when you need to store a lot of data for offline use (ex. videos) or extensively use the device’s capabilities (camera, file system, accelerometer, etc.). Besides PWAs, we have also developed native apps and, having written a lot of JavaScript code, React Native was the perfect choice for us.

In this article, I will include some key points about React Native – mainly things that I wish someone would have told me when I started developing on this platform :).

What is React Native?

If you have worked for a long time on web projects like I did, you’re probably wondering what exactly is React Native? In their own words:

With React Native, you don’t build a “mobile web app”, an “HTML5 app”, or a “hybrid app”. You build a real mobile app that’s indistinguishable from an app built using Objective-C or Java.

Before, when we were packaging apps with PhoneGap, the app was still running in a webview. With RN, we get actual native code, which can greatly improve performance, especially when working with large amounts of data.

Since we’re still writing JavaScript code as we do for web apps, how does this even work?

It turns out that each RN application has two execution threads (see Figure 1). The main one, which exists in all native apps, is responsible for displaying the UI and handling user gestures. The second one, which is specific to RN, executes the JavaScript code in a separate JavaScript engine.


Figure 1: React Native Architecture
Source: https://wetalkit.xyz/react-native-what-it-is-and-how-it-works-e2182d008f5e

The application’s logic is implemented in JavaScript and the two threads communicate through a bridge.

Even through RN doesn’t yet have a 1.0 version (the last version is 0.55 at the time of writing this article), a lot of big names are already using it in production (ex. Facebook, Instagram, Airbnb, Skype, etc.). That just goes to show how popular it really is.

PWA or Native Application?

Now that you know what React Native is, you’re probably wondering if it’s worth investing your time & effort into creating a native app. After all, we can now add offline mode support and web push notifications for PWAs. Even though RN uses JavaScript, you’ll still have to learn how it works and go through the hassle of submitting the app to the App Stores. In order to help you make a decision, I have listed below a couple of specific use cases where we have opted for creating a native app instead of going for a PWA:

  •  The app makes extensive use of the device’s native capabilities: camera, accelerometer, geolocation, etc.
  • Offline mode requires storing a lot of data (ex. video files).

If these two features are a key part of your application, go with a native app. React Native also offers the advantage of using the same code base for the iOS & Android versions, with a few minor changes that we’ll tackle in the next sections.

React for Web vs. React Native

A React Native app has a similar structure to a React web app, so if you have worked with React before, it’s pretty easy to make the switch.

When you start a new project with React, you will probably choose a bundler (ex. Create React App). React Native comes with everything that you need to create an app build. You just have to keep in mind the following things:

  1. React Native has its own set of UI components

    In the sample code below, you can see the difference between a RN component and a React web component. RN has a set of built-in components that can be used in pretty much the same way as JSX (a View is similar to a <div> tag, a Text component works as a <span> or <p> tag and so on).

    Example of a React web component:

    Example of a React Native component:

  2. Styling syntax is similar to CSS

    You might think the styling is done in CSS, but it’s not :). All properties must be written in camel case (instead of using dashes). You’ll also notice that the styling is not cascading, meaning the styling from a parent component is not applied to its children.
    In addition, there are no mixins and the functionality is limited. We can use flexbox to align components, but that’s pretty much it.

    React styling (web):

    React Native styling:

  3. The data layer is identical

    The data flow in a RN app is identical to a web app. The components don’t care how they are getting their props, so you can keep using Redux or Mobx as before.

Getting Started with React Native

There are two ways to create a new React Native app and they are both documented here.

  1. Create React Native App

    This is the equivalent of Create React App. The process is the same – install CRNA globally and create a new app with:

    create-react-native-app AwesomeProject

CRNA is useful for quickly starting a project, especially if you haven’t worked with React Native before. The app is run through another app called Expo. However, it has an important limitation: if you want to use a NPM package that has native dependencies, you have to either:

  • eject the project from CRNA (thus eliminating the CRNA dependency entirely) and continue running it with the native CLI or
  • limit the app to using system functionality through the Expo SDK.
2. Build projects with native code

This requires more steps to install watchman, Xcode and the RN command line interface. You create a new app with:

react-native init AwesomeProject

I personally recommend getting started with CRNA, and once you get comfortable with RN, ejecting the app and continue using it with the native CLI.

Running a React Native app

All RN apps require a device or simulator to run. I prefer to use simulators for iOS (they will be installed together with Xcode), as they are pretty stable and closely mimic the device, and work with the device only in particular cases (for example to test a barcode scanner).

For Android simulators, the RN bridge is much less stable. Although this might change in the future, we had problems when using the simulators, they were crashing all the time, and we ended up testing directly on the device.

Routing & Navigation

Navigating between app screens plays an important part in any application. Most React web apps use react-router for switching between pages. React Native has its own libraries for managing app screens and at first glance they look similar to their web counterparts. However, you have to keep in mind that a native app can have multiple screens mounted on top of each other. This might seem like a minor detail, but properly handling the navigation stack is an important task – screens in the stack will remain active even if they are not visible. In some cases, this can cause crashes – for example, when the camera is mistakenly opened multiple times.

There are several navigation libraries that you can use with React Native:

  1. react-navigation – created by the community;
  2. react-native-navigation – authored by Wix;
  3. react-native-router-flux – this one is based on react-navigation, so I’m not going to cover it here.
React Navigation

This is the solution that React Native recommends. It’s worth noting that this library is based entirely on JavaScript:

React Navigation does not directly use the native navigation APIs on iOS and Android; rather, it re-creates some subset of those APIs.

This means that the screens are not actual native screens, but rather they are being simulated. This is an advantage if you don’t want to eject the app from CRNA. On the other hand, the performance is reduced and the library itself has many other issues that we encountered when we tried building a more complicated navigation stack. As a disclaimer, we haven’t use react-navigation v2 so far, our conclusions are based on v1. V2 has just launched, it’s possible it fixes some of these issues:

  • Nested navigators are difficult to configure. In an app that we built, a drawer (side menu) and header bar were displayed only after the user logged in. We had to use the DrawerNavigator as a root navigator because we couldn’t get rid of the header from the StackNavigator on the screen with the login form (see code example below).React Navigation – Nested navigators:React Navigation – Switching between screens:
  • We ended up using a lot of modals, to avoid making changes to the navigation stack.
  • It was really slow on Android.
  • To handle the back button event on Android, we had to use Redux for retaining the navigation state.

React-navigation v1 has caused so many issues for us, that we ended up replacing it with react-native-navigation.

React Native Navigation

React-native-navigation was created by Wix and uses native screens. This dependency means that you’ll have to eject your app from CRNA if you plan on using this library. See some code examples below.

React Native Navigation: Register screens

React Native Navigation: Switching between screens

Some of the limitations / issues that we encountered while working with react-native-navigation are:

  • We had to choose between a single screen or a tab based app. In one case where we had a screen with tabs only for the logged in user, we had to create our own footer bar with tab buttons.
  • There’s a lot of code for push(), pop(), resetTo() that is repeated from component to component and becomes redundant.
  • You can’t reset to a particular screen in the stack. resetTo() wipes out the entire navigation stack.
  • On Android, a double tap will mount a screen twice. This can cause huge problems (if the camera is mounted twice, it will crash the app). You can check this thread for a detailed description of the problem and some suggested fixes.

React Native Navigation v2 is currently in alpha and promises to solve at least some of these issues. You can check out their roadmap for a more detailed overview.

Overall, none of these libraries worked perfectly for all use cases. I feel like the react-navigation library is currently recommended by React Native because it was created by the community. At this point, we advise to use react-native-navigation which is more stable, at least until we know for sure that react-navigation v2 has addressed some of the blocking issues from v1.

UI Components and Styling

As I mentioned in a previous section, React Native has its own set of UI components. They are pretty basic, so if you’re looking for something more “complicated” (for example a multiple select box), you’ll have to either write your own components or search on NPM.

I recommend staying away from packages that are not actively maintained. RN is being updated on a monthly basis, so you’ll end up cloning & maintaining these packages yourself.

In the apps that we have developed so far, we have either created our own components or used NativeBase (see figure 2). We’re looking forward to seeing more UI kits such as NativeBase being created.

Figure 2: Native Base – iOS version

When it comes to styling, the app looks almost the same on iOS and Android, with the exception of native components (ex. pickers) that are platform specific. You can import Platform.OS from react-native and add some conditions depending on the device type. This is similar to doing browser specific styling.

Do not rely on the device’s screen size – there are so many Android devices out there, that it’s impossible to correctly align UI elements based on that.

Offline Mode

An article about React Native would not be complete without mentioning one of the most important app features: offline mode.

In our PWAs, we have used Redux in combination with redux-persist to store data for offline usage. The difference is that web apps use the browser’s local storage, while native apps use Async Storage or the filesystem storage.

There is one important caveat for Android apps: the async storage is limited to 2MB per key. We were using Redux, and some reducers were stepping over this boundary. When this was happening, the whole reducer key was being wiped out from the store without any previous warning. As a workaround, we ended up using the filesystem storage, which is slower compared to the async storage.

Also, just because it’s a native app, that doesn’t mean you have access to unlimited processing power :). If an API response is too big, it can exceed the memory limit and the app will crash. This will happen more often on older devices, so it’s difficult to catch / debug.

Push Notifications

Adding push notifications support was quite simple for both iOS and Android apps. We had previously integrated with OneSignal for our PWAs and we decided to stick with it for native apps.

You’ll have to follow the setup guide in order to make it work, you’ll notice that is pretty well documented. Also, the OneSignal dashboard is very user friendly.

Upgrading to Newer React Native Versions

This is where React Native really sucks! There is a guide that you can find here, but I can tell you from personal experience that changing your RN version is a pain. It basically ruined our Info.plist file for the iOS app, so we ended up creating a new app, adding all NPM packages / dependencies and copying over the app folder with our code. I’m really hoping that React Native will solve this issue in the future. Since new versions are released on a monthly basis, it would be useful to have an easier way to keep it up to date.

App Stores Approval Process

It’s worth noting that the approval process is the same for all native apps, so you don’t have to worry about your app being implemented with React Native.

iTunes

iOS apps are signed & uploaded directly from Xcode. You’ll have to:

  • Create an account on developer.apple.com.
  • Create provisioning profiles & developer certificates and add them to your Keychain app.
  • Create the app bundle ID on iTunes.
  • Generate the app archive from Xcode (make sure to use the same bundle ID as on iTunes).
  • Upload the app to iTunes (also from Xcode).

Once the app is uploaded to iTunes, all app developers that have access to the same account will be able to install it through TestFlight. If you want to allow access to other email addresses, you’ll have to go through beta review.

Make sure that permissions messages are explicit – if you’re asking for permissions without specifying exactly why you need them, your app could be rejected.

Google Play

The build process for Android apps is much easier compared to iOS – you’ll just have to generate a key, sign your app with it and upload the apk from the developer console. Again – be careful when requesting permissions and eliminate all that are not required by your app.

When you’re ready, you can submit your app for review. It usually takes 2-3 days to approve it on iTunes and 1-2 days for Google Play – but don’t count on this. If your app needs to go out at a specific date, make sure you’re submitting it with plenty of time to spare.

Conclusion

The React Native ecosystem is constantly evolving, so it’s very important to do your homework before choosing a particular library. I also encourage you to write unit tests – especially for apps that are constantly being updated, tests are a key component of keeping things under control.

Also, if you stumble on a particular problem, take a look at the issues posted on Github. You’ll most likely find some answers in there :).

Happy coding! 🙂

 

CTO and Co-founder at Appticles

Leave a Reply

Your email address will not be published. Required fields are marked *