A React Native kick start for existing Native projects

Neel Bakshi
Apr 15, 2020 · 25 min read

React Native — the prodigal son for all mobile development! For a long while now I had been putting off my encounter with React Native just because of all the horror stories surrounding it. In the past few months I’ve been using it I formed a few opinions of my own regarding the SDK (remember these are just opinions and need to be taken with a pinch of salt and pepper and probably some marination.). In this article I would like to share these opinions and also help all you people out there who would want to integrate React Native into your existing app seamlessly so that you do not have to go through the process of trail and error before you actually have a screen built in React Native. This is not going to be a tutorial on React Native per se but will touch upon the other topics which are not so much talked about in terms of an existing native codebase.

This article will be broken down into the following sections:

  1. Why React Native — I’ll be writing only the good parts here. The discussion on my opinions come later.
  2. Project structure — A little bit of git stuff and folder structures for smooth sailing.
  3. Project Setup — Includes a little description of how your react native setup should look like along with how your existing code-base will integrate it into their own project
  4. Native Modules — just an introduction/few pointers on understanding it better.
  5. Build process — for development ,testing and release
  6. Codepush — Ship features out without doing an actual AppStore/PlayStore release.
  7. Opinions — This section is going to be my opinions on how my experience has been with React Native

This is going to be quite a long article, therefore I suggest you to take breaks while reading each section, otherwise it might feel like a lot to take in one reading since I touch on a lot of different topics while setting up the code-base. I would have loved to break the article into different parts but that felt like it would break the continuity. So if you’re ready ladies and gentlemen, get your coffee and let me take you on this adventure!

Why React Native?

  1. Decrease in shipping time — Since writing code in just one place leads to that feature being developed in both iOS and Android, just one developer is enough to develop the feature on both platforms, or if you’re just one developer working on both, this translates into you spending lesser time to develop that feature for both platforms.
  2. Frequent updates — Not to be confused with using it as a platform to ship untested code and ship bug fixes incrementally. This feature of the React Native is best used to be able to do frequent changes on layouts or features to make sure your business for the app grows. Keep your analytics ready for these features and using whatever data you collect for it to make changes so that you figure out what works best for your customer. Also genuine bug fixes should be shipped!
  3. Hot reloading —This was pretty nifty when it came to making changes. No waiting for you to build the app before you can see your changes. Just save your file and voila!.

There were other advantages as well like the open source community being very active, performance being great, building modular components etc. but those points are either to be expected from the SDK(and the community has done an extremely good job of it) or these are things that you already find in our native development environments.

Project structure

  1. Have your React-Native project as the root folder with two subfolders ios and android in it which contain your existing native code
react-native-codebase
|- ios
|- android
|- other-react-native-codebase-files

2. Or Vice Versa. You maintain your native code bases separately and have the react native codebase inside the iOS and Android folders.

ios
|- ios-files
|- react-native-codebase
android
|- android-files
| - react-native-codebase

I tried both of these setups and I found the first one to be more seamless than the second one. The second approach even though it makes more logical sense if you want to maintain separate code bases since you do not know if you’re going to really move everything to react native, will end up giving you some integration problems for which you’ll probably have to write scripts etc. to make sure things like auto-linking and building the project works.

For example: the problems I faced with the second setup was that while building the project I had to write build scripts in my iOS codebase (android worked fine strangely) where I had to insert a build phase script to do some directory changes to make sure the the bundle was building properly.

Auto-linking was also a problem in iOS. A little bit about auto-linking — Let’s say you’ve decided to use a react native library which has native code that needs to be integrated into your code base for you to use it(lots of libraries have it). For iOS you’d expect this to be integrated when you’re installing your pods and for android you’d want it to happen while you run your gradle sync. With the second setup, the iOS project was not able to auto-link the files properly from those libraries and I had to again spend some time to figure that out and fix it.

Bottomline — Safe method is Method 1, but if you’re interested in having the second kind of structure you might have to go through some loops.

A little bit about the git structure — There are three ways here:

  1. Git Submodules
  2. Git Subtrees
  3. Mono repo

I’m not going to get into the debate of which is better and which is not. It all depends mostly on how you use it. There are definitely some advantages/disadvantages for all. Although I went with the submodule way. I did read a lot of hate for submodules but I really haven’t found it to be that bad (till now). You should do a little research on this before you decide on the git structure you would want to move ahead with.

Project Setup

  1. package.json — Where you’ll write all your dependencies you need for your project.
  2. index.js — The file that is your entry point to the entire react-native code during runtime.
  3. metro.config.js — The file where you define the configuration for your metro bundler, that bundles your react-native js files. Ideally the default settings should work, but if you’re using the metro-react-native-babel-preset with non-default presets or have more transformers (eg: react-native-svg-transformer) then you might have to do a little more setup.

4. The rest of your files wile be your source code files which for which you can follow whatever folder structure you want.

Transformers

Let’s start with Babel. Babel is a trans-compiler who’s job is to make sure whatever standard(ECMAScript 2015+) you write your JavaScript in it will make sure it works fine on all browsers (because different browsers/javascript engines might read js differently depending on the standard that they support) by making it backward compatible. By default metro has its own babel configuration setup and this should be enough for you.

Coming to transformers, they are an important part of how different types of files are transformed during bundling. I shall explain this with the help of an example and you can extrapolate it to different transformers like typescript transformers etc.

Let’s use the simple case of react-native-svg-transformer. This library is used to transform .svg files for bundling so that they can be read by you JavaScript code during run time. Your files should look something like this in that case.

You will have a metro.config.js file in your root directory with the following code:

metro.config.js

Create a new file called transformer.js (or whatever for that matter but make sure you include it in the metro.config.js file against the babelTransformerPath key, as shown in the metro.config.js sample file above), and put the following code in the transformer.js file.

All the previous code is doing is that it is telling that in case your file type is svg use the svgTransformer else use the babel transformer(upstreamTransformer).

Most of the transformers that you keep adding for ex: typescript transformer etc will be added now in this tranformer.js file itself as a new elseif condition.

Just to make sure we’re all on the same page your project should look something like this if we’ve agreed on Project Structure mentioned above as well.

|
|- src
|- metro.config.js
|- package.json
|- transformer.js
|- ios
|- android
|- //other files like .gitignore, .gitmodules, yarn.lock etc.

Integration the Framework in your native codebase

The first step in going to be to add react-native as a dependency to your project. You can do so by going to your react-native project folder and running

yarn add react-native@0.61.5

At the time of writing this article the latest version was 0.61.5.The next step is going to be adding other dependencies in your package.json , which can be added the same way — using the yarn add command. For the newbies here you can find the name of the dependency usually in the npmjs website. Now once you’re done adding the dependencies you will have to do a little bit of setup in your native code-bases as well. Now before you move on to this here is a very helpful link that will give you pointers on what are the different things that need to be done during the initial integration.

React Native Upgrade Helper — Your go to link for whenever you upgrade your React Native version in your app.

For you first integration visit the upgrade helper link given above, and select your current version as the earliest version available and select the version that you’ve installed as the version that you want to upgrade to

Something like this!

Now once you click on upgrade it’ll give you a diff of the entire React Native repo.

iOS

Scroll down to the Podfile section once you’ve clicked on the show me how to upgrade button on the upgrade helper link and copy all the relevant Pods mentioned there into your Podfile. There are two commands that you need to pay attention to here and they are require_relative and use_native_modules. These are going to help you with auto-linking which is, whenever you add a new dependency to your React Native codebase, and if they have any native components that need to be integrated as well, this command i.e. use_native_module will help you. You will just need to run pod install and that dependecies native components will be integrated into your codebase as a regular pod.

Android

  1. android/build.gradle
  2. android/app/build.gradle

Integrate the changes that you see in these two files in your project after reviewing them. The diff on the website also includes a few changes with respect to making the gradle file a little better(eg: compileSdkVersion, signingConfigs etc) which you may or may not choose to include but lines including react-native, hermes etc need to be included in your project.

The Bridge and Native Modules

The process of creating a native modules is very simple. I shall explain a little bit in detail about the following steps:

  1. Write native code and expose it to your react native code.
  2. Use the NativeModule library exposed by react native on the react native side to access these functions.
  3. There are only certain kinds of parameters that you can transport between react-native and native so keep that in mind. It’s available in the documentation link specified above.

iOS

You can initialize this BridgeDelegateClass wherever you see fit. It can be as soon as your App launches or you can initialize it lazily when needed. Moving on to exposing native methods to the React Native side

If you’re writing code in objective-C you should not find a lot of difficulty, but if you’re writing Swift then you will need two files to do the actual export:

Now this module SomeNativeBridge and its function doSomething is exposed over to the react-native side. One thing to remember about the swift functions is that if your function takes in parameters like mine does, the first parameter needs to have an underscore before it (basically the first parameter has to be an non-external named parameter).

Android

  1. Create a ReactContextBaseJavaModule class which is where you will define your methods that you want to expose.
  2. Create a ReactPackage class which exports the Module class defined in the previous step.
  3. Export this ReactPackage class while creating your reactInstanceManager.
Step 1
Step 2

Now the last step is where you create the reactInstanceManager(which is similar to the BridgeDelegateClass that we had created for iOS) and expose SomeReactPackage. Now the decision is upto you as where you want to initialize the reactInstanceManager. Let’s say you want to do it on the onCreate of your Application class, the code is going to look something like this:

Now these methods are accessible from the React Native side as so:

This concludes the Native Modules setup.

Build and Configuration Process

Similarly for your React Native setup to work seamlessly, there are going to be three modes in your native environment which you should be building your app:

  1. Debug — During development
  2. Release— for builds that you distribute internally within your organisation. You can rename it to whatever you want. Another reason for this type is also because it is required by CodePush in Android.
  3. Production — builds that you distribute in Production.

This is required if you want to integrate CodePush later on. If you do not want to integrate CodePush, you can make do with just two environments(Debug and Production). I’m going to break this down into iOS and Android sub sections to explain things better.

iOS

Different Configs for different Build Schemes.

Android

Now these configuration values are not accessible in React-Native because your iOS and Android projects may have overlapping config values with different names and it does not make sense to have them duplicated.

Your Build Schemes(iOS) and Build Variants + Flavors (Android) are also not accessible on the react native end. Therefore in order to manage configurations you can easily use a library called react-native-config which will help you in that purpose. You will also find a section which will help you integrate different config files defined for the different environments.

Android setup for Development

  1. Setup your development/debug environment for the React Native error view.
  2. Remove a few non production necessary permissions added inside the React Native library.

Android Error View Setup

  1. You need to add the following activity in your AndroidManifest.xml file
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

This is the activity which shows up showing all your errors.

2. You also need to add a permission in your manifest file

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

This permission allows react native to draw over your app to show the DevSettingsActivity. After this you need to write a few lines of code to make sure Activity class which is going to hold your ReactRootView can display this DevSettingsActivity. You need to paste the follow code in your onCreate method of this Activity:

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this) && BuildConfig.DEBUG) {
val intent: Intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:$packageName"))
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE)
}
}
// More stuff for initialisation if needed

What is not explained properly is the fact that this permission needs to be removed for production builds. For that we shall use the Manifest Merging that Android Studio provides us with:

Create a new directory in your src directory with the name debug (or whatever name you’ve given your build type for your debug builds in your gradle file, although it is usually debug, you can extrapolate the same logic for other build types as well) and create a new AndroidManifest.xml there. In that paste the following code

debug/AndroidManifest.xml

While building Android studio will make sure that these are merged only for debug builds and your release/production environments will be unaffected by this.

Remove non-production necessary permissions

android.permission.READ_PHONE_STATE
android.permission.WRITE_EXTERNAL_STORAGE
android.permission.READ_EXTERNAL_STORAGE

These permissions are integrated from within the React Native library itself so in order to remove these permissions you need to add the following lines to your AndroidManifest.xml

main/AndroidManifest.xml

Now in order to actually create a build out of all the code that you’ve written you need to create a jsBundle file.

Bundle the React Native code

iOS

npx react-native bundle — entry-file index.js — platform ios --dev false --bundle-output ./ios/main.jsbundle — assets-dest ./

Now this will output the jsBundle file into your iOS folder but it is still not going to be integrated when you build you iPA. In order to do that you will need to drag and drop this jsBundle file into your XCode and then add it in the Target Membership section.

Add the jsBundle and add it to your target membership.

Make sure that you’ve checked the box corresponding to your target otherwise it will not be bundled when you create your iPA.

You can also automate this process by adding a Build Phase Run Script with the following contents since the above step might become tedious everytime you’d want to do an ad-hoc or production release:

npx react-native bundle --entry-file index.js --platform ios --dev false --bundle-output $CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/main.jsbundle --assets-dest $CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/

Android

npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/

This will create your jsBundle for Android and now if you create an APK, since the jsBundle is stored in your assets folder it will be integrated into your APK by default when you create one.

CodePush

This is Microsoft's tool which helps you upload your react-native JS bundles so that it can be deployed to the apps already installed on your customer’s devices.

  1. Login to AppCenter and login with your credentials.
  2. Create an Organization
  3. Create two apps one for iOS and one for Android. Both need to have ReactNative as their platform. You can go ahead and select whichever Release Type you want depending on your needs.
Create app on Codepush

Now the setup that appears is for including AppCenter for analytics and crash tracking. You can go ahead and do that if you want to. I shall skip ahead to the react native setup.

The reason I’m writing this down here is because the documentation felt convoluted. I had to keep jumping between sections to figure out what was happening where. In the following sections I will try to outline where the relevant information is present and provide tiny bits of information as and when required.

CodeSigning

This section in the document tells you exactly what to do to create the pair. Make sure you save both the public and private pem files in your system. Let’s call them codepush-public.pem and codepush-private.pem

Now on to storing the public key on the apps.

iOS

Copy the contents of codepush-public.pem and paste them in your Info.plist for the key CodePushPublicKey

Android

resValue "string", "CodePushPublicKey","Contents of codepush-public.pem file"

CodePush Integration

Before all of this it’s better you install the code-push-cliand appcenter-cli. So that it is easier for you to follow through with the setup guide.

npm install -g code-push-cli
npm install -g appcenter-cli

The setup follows the following steps:

  1. Install Codepush

Run npm install --save react-native-code-push in your react native directory.

In iOS run pod install and auto-linking should take care of the rest.

In Android add the following line to your build.gradle file

apply from: "../../node_modules/react-native/react.gradle" //this is already added probably, if not add this
apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"

2. Update your Debug path to the JSBundle to CodePush URL.

In your code you are probably listening to either the react native port or have given the path to the JSbundle that you’ve built in your Bridge class. This needs to be updated to the URL which CodePush exposes.

iOS

Android

You might remember this piece of code from the Android Native Module section code.

3. Add your deployment key to your project.

First you will need to login to your code-push portal. Remember you had created an account on AppCenter. With you logged into the portal on your browser you can login using

code-push login 

If you’re using appcenter-cli the command is

appcenter login

Then just follow the instructions. Pretty simple. From now on I’ll write down both the code-push cli commands and appcenter cli(another cli tool which is a super wrapper over code-push cli, it has lots of other commands unrelated to codepush as well) commands so that people using both can benefit from it.

To access the list of all your apps run

code-push app list
(or)
appcenter apps list

Now you’ll be able to see your apps as such

Output of app list command

Now just copy the name. You can access your deployment key by running the following command

code-push deployment list -a <OraganisationName>/<appName> -k
(or)
appcenter codepush deployment list --app <OraganisationName>/<appName> -k
eg: From the app list output that you get from the previous command you can copy and paste it here.
code-push deployment list MyPersonalOrganisation/MyOwniOSApp -k
Output of the deployment list keys

Now to add the deployment keys in your project.

iOS

You can store these values in your .xcconfig file and then pick up the correct value depending on which mode you’re building your app.

Android

resValue "string", "CodePushDeploymentKey", '"DeploymentKey"'

now like I had previously mentioned, you need to have three modes for your build, which are known as buildTypes. Your build.gradle should look something like this

Now the setup for codepush is done. Now over to initialising it on the react-native side.

In your index.js in your react-native project you can add the following code so that as soon as your jsbundle loads in your native project, a CodePush sync will work.

import CodePush from 'react-native-code-push';
import Screen from './src/Screen';
AppRegistry.registerComponent('YourScreen', () => Screen);
// more registers
CodePush.sync({
checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME,
installMode: CodePush.InstallMode.ON_NEXT_RESUME,
updateDialog: true // shows a dialog to the user when there is an update
});

You can choose from the different options available in CheckFrequency and InstallMode as to what makes most sense for your users.

Releases

appcenter codepush release-react -a <Organisation>/<App> -d Staging -k codepush-private.pem --description "Some description"

The above command pushes to Staging. You can also push to Production from here.

codepush-private.pem — this was created when you were performing the CodeSigning section.

This section in the documentation shows the different parameters supported by the release-react command.

That’s it for CodePush. If you’ve followed up with me till now you should have the most basic setup ready to publish your app on the AppStore/PlayStore.

Opinions

  1. Tedious setup for existing projects — The setup took a good amount of time for me. Even with the documentation I had to juggle around a lot to figure out what works and what doesn’t through a lot of trial and error. I hope this article helps you out surviving this problem.
  2. Obscure error messages —Error messages were misleading in a lot of cases. The underlying problem would be completely different and the error shown would show something else. Only if there were problems in the JS code written by you(i.e. your own source files), you’d get a proper stack trace, otherwise it takes quite some time to understand what is going wrong.

Eg: I was setting up my project and getting this error saying that unrecognised selector sent to instance of my AppDelegate saying that it does not have a property called window. Turns out that the solution to the problem was that on the React Native end I hadn’t imported the AppRegistry properly. You can see how disconnected the entire situation was.

3. Performance issues — In my project I had developed a very basic screen in React Native. It did not have a lot of animations or jumping between native and react-native code through the bridge. Now for this scenario I did not find any kind of performance impact on iOS devices. Some low end Android devices did suffer but not a lot(they probably would have even with native code). I do have plans to test it out on more complex screens but as far as I keep reading different articles, I see people pointing out some problems suffered in performance due to a lot of communication via the bridge. Although I have also seen people figure out solutions to these problems and sometimes these solutions are not related.

4. The curse associated with the boon of having all code in one place (i.e. both your Android and iOS projects). At first it might seem like a great idea that you have to write code just once and reap the benefits of it on both the platforms. What I later realized was that this comes with the added burden of having to test both your projects if you decide to change your code somewhere. You can’t decide to make a release just by testing it on the platform you’re making the release for. Bug fixes etc have to be tested on both platforms(web as well if you decide to use it for your mWeb + Desktop screens as well) before you decide that it works. Now to tackle this problem you might decide to start using the Platfrom API which React Native provides, but make sure you do not end up with a code base riddled with these platform checks. You will encounter this a lot when you’re styling your components.

5. It does make a few hard things easy, but it also makes a few easy things pretty hard. There are certain scenarios where you’d experience the fact that a particular thing could have been achieved very easily if you were writing native code. eg: it could be as simple as trying to achieve a custom dashed line border implementation on a view. I could not find an easy way to achieve this and had to resort to a native implementation(the other solution would have been to use a third party library which also used native code). Coming from a strong native developer background my initial reaction was to get irritated, although I think in such cases it is better to look at the bigger picture. If you think React Native is not worth the hassle even with these advantages then you are better off staying away from it (at least for the implementation of that feature or screen).

6. Over dependence on third party libraries. React Native is heavily dependent on the community. Being an active part of it is going to work in your favor, but in case you’re not, then you’re going to be dependent on other developers for whatever features that you want. This can include simple things like managing configurations, implementing screen navigation etc. There are two problems that I can associate with this. The first one is not actually a problem but a mindset change. Coming from a native background you’d expect these things to work without third party libraries(i.e. directly from React Native and not the community) since these were available to you directly from your native SDKs. This is a mindset change that you will have to undergo that with React Native it’s the community that keeps even the simple things working. The second problem that I found was that given that the community is huge, you will find lots of popular libraries doing the same thing which kind of creates a dilemma as to which library you need to choose. For eg: with Navigation I was confused between three choices wix/react-native-navigation and react-native/react-navigation and airbnb/native-navigation. Having so many choices just increases the time you need to spend deciding which library to integrate even for something that as native developers we would expect to not be dependent on third party libraries.

Now the best course of action that I would suggest is that you should try it out for yourself to see how this framework helps you. Take the less risky way out first by testing the waters before you completely plunge yourself into a full react-native mode and start moving everything to it. Move step by step. Build simpler screens first and then increasingly tougher screens in it to see how things work and if you’re able to cope up with the problems that you’re facing.

Sometimes you might face a problem that you’d know how to solve very easily while writing native code and you’d be pulling your hair thinking is this even worth it. The above suggestion is to save you from going down that road.

Every technology that you use is designed to help you reach your goal in someway. While fulfilling that purpose the technology makes certain trade-offs. If you believe that you need to have a one stop solution for whatever it is that you’re trying to achieve you will either have a very hard time building it or a very hard time to dealing with yourself when things start to go wrong.

The same things goes with React Native as well. It has its own advantages and disadvantages. You being the owner or maintainer of your code-base needs to take these decisions as to when the advantages outweigh the disadvantages and use it appropriately. Reconsider your decisions if you have to.

My personal opinion is that this framework works best for scenarios where you want to build screens whose designs you might want to change frequently to figure out which layouts are working best for your customers and your business. An added benefit to these screens will be that you can publish bug fixes faster to these screens, but you should not use this reason the other way round i.e. you decide to build a screen in react native just because shipping bug fixes are faster. Ideally your development + testing process should make sure that bugs that have slipped into production should be as less as possible. Keep your analytics setup ready with such screens and experiment at a faster pace.

Conclusion

The reason I decided to write this article was because I could not find a comprehensive guide to integrate React Native in my existing Native apps. The documentation on this topic is either extremely verbose where I was just juggling between sections thinking about what should I do next, or not well documented, and to find the solutions for those bugs I had to google, get into detailed GitHub issue discussions to figure out what worked for someone and what didn’t and then decide what works for my use case.

I hope this article helps you out if you’ve decided to go forward with React Native.

— Neel Bakshi, Mobile Engineer, Headout

Passionate about Swift and iOS development? Join us!! We’re hiring!

The Startup

Get smarter at building your thing. Join The Startup’s +793K followers.

Sign up for Top 10 Stories

By The Startup

Get smarter at building your thing. Subscribe to receive The Startup's top 10 most read stories — delivered straight into your inbox, once a week. Take a look.

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Neel Bakshi

Written by

Guy who handles everything mobile @headout among other things! Ex @practo

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +793K followers.

Neel Bakshi

Written by

Guy who handles everything mobile @headout among other things! Ex @practo

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +793K followers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store