Fixing React Native issues and happy deploy to Bitrise, Fabric, CircleCI
Tags: reactnative, bitrise, circleci, fabric
Published: 2018-09-25 05:49:05

---
title: Fixing React Native issues and happy deploy to Bitrise, Fabric, CircleCI
published: true
description: 
tags: reactnative,bitrise,circleci,fabric
---

![](https://cdn-images-1.medium.com/max/1600/0*7vKg_mdUkSLfvPVz.jpg)

This post is about implementing QR code scanning, fixing building issues, continuous integration with [Bitrise](https://www.bitrise.io/), crash reporting with Fabric and how to tame React Native projects into source control. React Native is hard, but dealing with [dependencies](https://medium.com/fantageek/what-is-create-react-native-app-9f3bc5a6c2a3) is harder. Just after a short while I collect tons of [issues](https://github.com/onmyway133/notes/issues?utf8=%E2%9C%93&q=RN), most of them are well known issues, but some take a while to figure out. Let’s start with a simple QR scanner feature.
[**What is create-react-native-app ?**
*As someone who comes to React Native from iOS and Android background, I like React and Javascript as much as I like…*medium.com](https://medium.com/fantageek/what-is-create-react-native-app-9f3bc5a6c2a3)

![](https://cdn-images-1.medium.com/max/3760/1*8hINdX6Zsomwy8u3e6QrQg.png)

## QR code scanner with react-native-camera

After integrating [Facebook](http://React Native Login Using the Facebook SDK) and [Firebase](https://medium.com/react-native-training/firebase-sdk-with-firestore-for-react-native-apps-in-2018-aa89a67d6934), my app needs QR code scanner feature. A quick search shows several libraries, but all of them seem to be just a wrapper around [react-native-camera](https://github.com/react-native-community/react-native-camera).

In an ideal world, we can just run npm install react-native-camera --save and react-native link react-native-camera to have a camera feature. We of course need to tweak a bit for each platform. Firstly with permission <uses-permission android:name=”android.permission.CAMERA” /> on Android, and NSCameraUsageDescription on iOS. Then there are some issues. The version I use is react-native-camera 1.2.0 . Future version of the library will hopefully fix the below bugs, but we all have to be ready for other bugs.

### [Duplicate module name: react-native](https://github.com/onmyway133/notes/issues/351)

    Loading dependency graph...(node:15556) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: jest-haste-map: @providesModule naming collision:
      Duplicate module name: react-native
      Paths: /Users/khoa/XcodeProject2/MyApp/node_modules/react-native/package.json collides with /Users/khoa/XcodeProject2/MyApp/ios/Pods/React/package.json

    This error is caused by a @providesModule declaration with the same name across two different files.
    (node:34363) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

This clearly states that there are 2 module with the same name react-native . Because react-native-camera use CocoaPods to install a project called RNCamera , somehow they brings with a pod React , which is why there is Users/khoa/XcodeProject2/MyApp/ios/Pods/React/package.json , which contains the name of react-native .

A quick fix is to change the name to something else, for example

    "name": "react-native-avoid-collision"

### [Argument list too long](https://github.com/onmyway133/notes/issues/352)

    Argument list too long: recursive header expansion failed at /Users/*k*hoa/XcodeProject2/MyApp/node_modules/react-native-camera/ios/../../../ios/build

This is because of recursive search paths in framework search path. If you open RNCamera.xcodeproj at node_modules/react-native-camera/ios/RNCamera.xcodeproj/project.pbxproj and look for Framework search path under Build settings, you can see that it is currently recursive . You should change $(SRCROOT)/../../ios/to non-recursive for both Debug and Release configuration.

![](https://cdn-images-1.medium.com/max/4264/1*KFKLuzMYTibCkehW6vAsWA.png)

### [The library com.google.android.gms:play-services-basement is being requested by various other libraries](https://github.com/onmyway133/notes/issues/354)

    org.gradle.api.GradleException: The library com.google.android.gms:play-services-basement is being requested by various other libraries at [[15.0.1,15.0.1]], but resolves to 12.0.1. Disable the plugin and check your dependencies tree using ./gradlew :app:dependencies.

Because I use [Firebase](https://medium.com/react-native-training/firebase-sdk-with-firestore-for-react-native-apps-in-2018-aa89a67d6934), it has conflict with react-native-camera. Simply because it needs Firebase for face and text detection. In my opinion this is nice to have, but break the single responsibility principle. A camera library should have camera feature only !!!
> GMV (Google Mobile Vision) is used for Face detection/Text recognition by the iOS RNCamera. You have to link the google frameworks to your project to successfully compile the RNCamera project.

A quick fix is to specify ext { googlePlayServicesVersion = "15.0.1" } to the project build.gradle

The usage is quite straightforward

    render() {
      return (
        <View style={styles.container}>
          <Camera
            style={styles.camera}
            aspect={Camera.constants.Aspect.fill}
            onBarCodeRead={this.onBarCodeRead}
            ref={cam => this.camera = cam}
          />
          {this.makeOverlayIfAny()}
        </View>
      )
    }

The onBarCodeRead is where we handle callback for QR code detection.

OK, all the issues seem to be solved. Let’s see how it builds on CI.

## Running React Native apps on Bitrise

I had good experience with [Bitrise](https://hackernoon.com/using-bitrise-ci-for-android-apps-fa9c48e301d8) for Android and iOS app, and since it also supports [React Native](https://blog.bitrise.io/how-to-set-up-a-react-native-app-on-bitrise), there’s no double in using it. In theory, we can just add add a React Native app and Bitrise can build iOS and Android apps. But in practice, there’s always issues.

### [Cannot find module ‘jest-haste-map’](https://github.com/onmyway133/notes/issues/360)

My app runs perfectly locally, but fails on Bitrise. I have contacted support but clueless. Since Bitrise supports Workflow with lots of steps, I can choose Run npm command to install missing module

![](https://cdn-images-1.medium.com/max/3396/1*sdO5TWK3dStoeDRTFz6ZnQ.png)

After running npm install jest-haste-map , I get

    Cannot find module 'jest-worker'

Then I run npm install jest-worker , I get

    Cannot find module 'jest-serializer'

Then I run npm install jest-serializer , I get

    Loading dependency graph...(node:74975) UnhandledPromiseRejectionWarning: Error: fseventsunavailable (this watcher can only be used on Darwin)
    at new FSEventsWatcher

### [main.jsbundle does not exist](https://github.com/onmyway133/notes/issues/363)

The other issue I get is

    main.jsbundle does not exist. This must be a bug with + echo 'React Native.

This mean there’s an error during bundling, so the main.jsbundle could not be found. Since it runs fine locally, I think I should manually generate bundle and track that to Git, since react-native init by default adds jsbundle in .gitignore . Let’s add the following to package.json , remember that the name field must be all lowercase and not contains spaces.

    "build:ios": "react-native bundle --entry-file ./index.js --platform ios --bundle-output ios/main.jsbundle"

It still fails on Bitrise. And with newer version of React, this is not needed, as React Native a bundling script in Xcode Build Phrase that can detect configuration and whether we use index.js or index.ios.js

    # Define entry file
    if [[ -s "index.ios.js" ]]; then
      ENTRY_FILE=${1:-index.ios.js}
    else
      ENTRY_FILE=${1:-index.js}
    fi

![](https://cdn-images-1.medium.com/max/3736/1*FP3yKJ4h1AurMGNfwICo5w.png)

    if [[ "$CONFIGURATION" = "Debug" && ! "$PLATFORM_NAME" == *simulator ]]; then
      IP=$(ipconfig getifaddr en0)
      if [ -z "$IP" ]; then
        IP=$(ifconfig | grep 'inet ' | grep -v ' 127.' | cut -d\   -f2  | awk 'NR==1{print $1}')
      fi

    echo "$IP" > "$DEST/ip.txt"
    fi

    BUNDLE_FILE="$DEST/main.jsbundle"

    $NODE_BINARY "$CLI_PATH" $BUNDLE_COMMAND \
      $CONFIG_ARG \
      --entry-file "$ENTRY_FILE" \
      --platform ios \
      --dev $DEV \
      --reset-cache \
      --bundle-output "$BUNDLE_FILE" \
      --assets-dest "$DEST" \
      $EXTRA_PACKAGER_ARGS

For Android, before React Native 0.19.0 we needed to manually run below to generate [bundle](https://proandroiddev.com/bundling-react-native-during-android-release-builds-ec52c24e200d).

    react-native bundle --platform android --dev false --entry-file index.android.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/
[**Bundling React Native during Android release builds**
*Automating the bundling of js resources for React Native during Android release builds*proandroiddev.com](https://proandroiddev.com/bundling-react-native-during-android-release-builds-ec52c24e200d)

Now we don’t need to manually run React Native bundle command, because there is [react.gradle](https://github.com/facebook/react-native/blob/master/react.gradle) as part of the build step. If you look at the app build.gradle, you will see
> The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
and bundleReleaseJsAndAssets).
These basically call `react-native bundle` with the correct arguments during the Android build
cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
bundle directly from the development server. Below you can see all the possible configurations
and their defaults. If you decide to add a configuration block, make sure to add it before the
`apply from: “../../node_modules/react-native/react.gradle”` line.

Nothing look suspicious here. I also tried [clearing cache](https://gist.github.com/jarretmoses/c2e4786fd342b3444f3bc6beff32098d), closing debugger, deleting app but it still failed on Bitrise. The thing that it runs perfectly fine locally make me thing there’s mismatch between tooling and environments between local and Bitrise.

### Matching tooling versions

Run react-native info to get versions of all the tools we use. Here I have

    React Native Environment Info:
        System:
          OS: macOS High Sierra 10.13.6
          CPU: x64 Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
          Memory: 94.97 MB / 16.00 GB
          Shell: 5.3 - /bin/zsh
        Binaries:
          Node: 8.11.4 - ~/.nodenv/versions/8.11.4/bin/node
          npm: 5.6.0 - ~/.nodenv/versions/8.11.4/bin/npm
          Watchman: 4.9.0 - /usr/local/bin/watchman
        SDKs:
          iOS SDK:
            Platforms: iOS 11.4, macOS 10.13, tvOS 11.4, watchOS 4.3
        IDEs:
          Android Studio: 3.1 AI-173.4907809
          Xcode: 9.4.1/9F2000 - /usr/bin/xcodebuild
        npmPackages:
          react: 16.4.1 => 16.4.1
          react-native: ^0.56.0 => 0.56.0
        npmGlobalPackages:
          react-native-cli: 2.0.1

So let’s make sure that we have the same node, npm, react-native versions. Luckily Bitrise has custom steps to change

![](https://cdn-images-1.medium.com/max/2000/1*Kh-FrpeUh5UnH-H6vG8Avg.png)

But even with same versions and configurations, it still fails on Bitrise 😕

## Trying with a clean git clone

The next [thing](https://discuss.bitrise.io/t/how-to-debug-your-build-locally-it-works-on-my-mac-pc-but-not-on-bitrise-io/392) that I should do is to delete the local repo and clone from remote again. And surprise, I actually get the same errors on Bitrise. It’s time to dig into the problems.
[**How to debug your build locally / "It works on my Mac/PC but not on bitrise.io"**
*If the build works on your local Mac/PC but not on bitrise.io, that's usually because of uncommitted / gitignored files…*discuss.bitrise.io](https://discuss.bitrise.io/t/how-to-debug-your-build-locally-it-works-on-my-mac-pc-but-not-on-bitrise-io/392)

Remember the workarounds I did to make react-native-camera works? To keep those changes, I track node_modules and Pods to git, as I [don’t](https://blog.npmjs.org/post/141577284765/kik-left-pad-and-npm) [trust](https://www.theregister.co.uk/2016/03/23/npm_left_pad_chaos/) the npm system and also I want to see the changes to any of my dependencies. This way the CI can just build my apps without npm install or pod install

The thing is, with this, we have to understand .gitignore extremely well, as it is based on patterns. Some files are tracked, and some files are not, and it can gives room for inconsistence between local and CI. So the logical move is to ignore node_modules and Pods and somehow still keep the workaround changes. Tracking dependencies are useless as when it changes, there’s bunch of changes in git diff and we probably don’t know (and don’t want to) what’s happening.

The lesson that I learn is to never track generated or downloadable stuff, like node_modules, jsbundle, Pods as they are not our concern.

We have package-lock.json and Podfile.lock so we should be on the same versions. The package manager should handle the rest. Yeah, hopefully.

![](https://cdn-images-1.medium.com/max/2000/0*Y_2csNNYoXnezSgI.png)

So, how to keep the workaround changes? I see there are several ways

### Fork modules

React Native has a bunch of dependencies, and any of them can have bugs, especially during version change, for example this [schedule 0.5.0](https://github.com/facebook/react-native/issues/21150) in React Native 0.57.0 . We can ope issues, wait, pray and hope that someone will come and fix. That is where we become dependants, we have no control over the things that we do. One way is to maintain your our own [fork](http://debuggable.com/posts/how-to-fork-patch-npm-modules:4e2eb9f3-e584-44be-b1a9-3db7cbdd56cb) with the changes we need
> Here is the proper way to do this while using npm to manage your forked version of the module:
> - Fork the project on GitHub
> - Clone the fork to your machine
> - Fix the bug or add the feature you want
> - Push your commits up to your fork on GitHub
> - Open your fork on GitHub, and click on the latest commit you made
> - On the page of that commit, click on the “Downloads” button
> - Right click on the “Download .tar.gz” button inside the popup, and copy the link (“Copy Link Address” in Chrome)
> - Open up your package.json file, and replace the version number of the module with the url you just copied
> - Send a pull request upstream (Optional, but this way you will avoid having to maintain that patch of yours against newer versions of the module you forked)

Or if we want to say goodbye to the [old fashioned forks](https://blog.callstack.io/say-goodbye-to-old-fashioned-forks-thanks-to-the-patch-package-3c63a62eea1c), we can use patch-package to apply patches
[**Say goodbye to old-fashioned forks thanks to the patch-package.**
*Have you ever been in a situation when you had to fork the library to apply some fix to your app? While working with…*blog.callstack.io](https://blog.callstack.io/say-goodbye-to-old-fashioned-forks-thanks-to-the-patch-package-3c63a62eea1c)

### Track inside ignored folders

Another way is to track just the files that we change. The .gitignore file generated by react-native init contains node_modules , which means that React Native team recommend not tracking node modules. But from git 2.8 , we can track files inside ignore folders. Here is how I keep my changes to RNCamera.xcodeproj inside node_modules . Use ! in .gitignore

    !node_modules/react-native-camera/ios/RNCamera.xcodeproj

This is fine, but too manual for a lazy programmer. Furthermore, it can be easily overwritten by the next npm install or pod install

### Shell scripting

Lazy programmers should automate boring tasks. Did you know that npm can run lot of scripts in many [events](https://docs.npmjs.com/misc/scripts).
[**scripts | npm Documentation**
*The place where all things npm are documented*docs.npmjs.com](https://docs.npmjs.com/misc/scripts)
> npm supports the “scripts” property of the package.json file, for the following script
> - preinstall: Run BEFORE the package is installed
> - install, postinstall: Run AFTER the package is installed.
> - preuninstall, uninstall: Run BEFORE the package is uninstalled.
> - postuninstall: Run AFTER the package is uninstalled.

We can use shell script. For example to change to name in Pods/React/package.json to avoid the issue [Duplicate module name: react-native](https://github.com/onmyway133/notes/issues/351), we can create a file call postinstall.sh , the name is arbitrary

    #!/bin/bash
    set -e

    # Fix Duplicate module name: react-native

    PODS_REACT_PACKAGE_JSON = 'ios/Pods/React/package.json'

    if [ -e PODS_REACT_PACKAGE_JSON ]
    then
        sed -i -e 's/"name": "react-native"/"name": "react-native-avoid-collision"/g' PODS_REACT_PACKAGE_JSON
    fi

    # Fix react-native-camera Argument list too long

    REACT_NATIVE_CAMERA_PROJECT = ./node_modules/react-native-camera/ios/RNCamera.xcodeproj/project.pbxproj

    if [ -e PODS_REACT_PACKAGE_JSON ]
    then
        sed -i -e 's/ios\/\*\*/ios\/\*/g' REACT_NATIVE_CAMERA_PROJECT
    fi

And add postinstall inside scripts in the root package.json

    "postinstall": "./postinstall.sh"

### Node scripting

There’s lot of code with shell scripting. We have node environment, let’s utilise that together with lots of packages like fs , so create a new file called postinstall.js . We can create function to encapsulate common functionalities.

    const fs = require('fs')

    function replaceJson(path, process) {
        if (fs.existsSync(path)) {
            let json = JSON.parse(fs.readFileSync(path, 'utf8'))
            json = process(json)
            fs.writeFileSync(path, JSON.stringify(json, null, 2))
        }
    }

    function replaceString(path, oldString, newString) {
        if (fs.existsSync(path)) {
            let string = fs.readFileSync(path, 'utf8')
            string = string.replace(oldString, newString)

    fs.writeFileSync(path, string, 'utf8')
        }
    }

    function write(path, string) {
        fs.writeFileSync(path, string, 'utf8')
    }

    // Fix Duplicate module name: react-native
    PODS_REACT_PACKAGE_JSON='ios/Pods/React/package.json'
    replaceJson(PODS_REACT_PACKAGE_JSON, function(json) {
        json.name = 'react-native-avoid-collision'
        return json
    })

    // Fix react-native-camera Argument list too long
    REACT_NATIVE_CAMERA_PROJECT='./node_modules/react-native-camera/ios/RNCamera.xcodeproj/project.pbxproj'
    replaceString(REACT_NATIVE_CAMERA_PROJECT, 
        '$(SRCROOT)/../../ios/**', 
        '$(SRCROOT)/../../ios/'
    )

    replaceString(REACT_NATIVE_CAMERA_PROJECT, 
        '$(SRCROOT)/../../../ios/**', 
        '$(SRCROOT)/../../../ios/'
    )

    // Android sdk
    const sdkDir = "sdk.dir = /Users/khoa/Library/Android/sdk"
    LOCAL_PROPERTIES = "android/local.properties"
    write(LOCAL_PROPERTIES, sdkDir)

Now every time after we run npm install , the workaround we need has been reapplied for us. This helps in other situations like the issue

    Error: SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable.

To solve this, we need to create local.properties with the SDK path. Mine is sdk.dir = /Users/khoa/Library/Android/sdk . This has my username in the path, which is very different than the username vagrant in Bitrise. I can’t seem to use ${HOME} or $HOME variable, so this change should not be tracked into git. You can see above that the script auto generates this file for us. So nifty.

Now everything works fine, and the build works fine too in Bitrise.

## Deploy to Fabric with Crashlytics reporting

To be honest, I almost ended up building locally and uploading the IPA/APK to Fabric since I couldn’t find a way to solve building errors on Bitrise. But right now we have to go with Fabric any way, as it has good installation page for testers, and also its Crashlytics reporting is so good. The good (or annoying) thing about Fabric is that we need to successfully build and run an app before it is registered into Fabric.

For iOS, we need to install the [Fabric macOS app](https://fabric.io/downloads/xcode) and follow the [instruction](https://fabric.io/kits/ios/crashlytics/install). I prefer dragging Fabric and Crashlytics frameworks into our Xcode projects more than using CocoaPods. Further more, if you archive project in Xcode, then you can manually choose archive and upload to Fabric. Simply choose the Archive tab in Fabric app.

![](https://cdn-images-1.medium.com/max/2000/1*jYjFvs1hCaAQUjTH6eIzpQ.png)

For Android, we need to install [Fabric for Android Studio](https://plugins.jetbrains.com/plugin/8002-fabric-for-android-studio). After installing, it is available in View -> Tool Widows -> Fabric

![](https://cdn-images-1.medium.com/max/6720/1*3eP-zzqkK2VM6731n2404Q.png)

The tool only sets up simple things. We still need to configure gradle, follow the instructions on [Install Crashlytics via Gradle](https://fabric.io/kits/android/crashlytics/install)

## Deploy to CircleCI 2.0

CircleCI has a lot of configurations, and it takes time to learn. Here is a simple configuration to check if the React Native project can build well for both iOS and Android. Create a file called config.yml inside .circleci folder.

    version: 2
    jobs:
      node:
        docker:
          - image: circleci/node:8
        steps:
          - checkout
          - run: npm install

    android:
        docker:
          - image: circleci/android:api-27-node8-alpha
        steps:
          - checkout
          - run: npm install
          - run: cd android && ./gradlew assembleRelease

    ios:
        macos:
          xcode: "9.4.1"

    # use a --login shell so our "set Ruby version" command gets picked up for later steps
        shell: /bin/bash --login -o pipefail

    steps:
          - checkout
          - run: npm install
          - run: xcodebuild -project ios/MyApp.xcodeproj -scheme "MyApp" -sdk iphonesimulator -destination 'platform=iOS Simulator,OS=11.4,name=iPhone X'

    workflows:
      version: 2
      node-android-ios:
        jobs:
          - node
          - android:
              requires:
                - node
          - ios:
              requires:
                - node

## React Native in Xcode 10

For now with React Native 0.57.0 , it does not [fully](https://github.com/facebook/react-native/issues/20492) [work](https://github.com/facebook/react-native/issues/19573) in Xcode 10 yet. I often get this error error: Multiple commands produce . In that case we need to append -UseModernBuildSystem=N to xcodebuild command. Or in Xcode, we need to use the old legacy build system. Go to File -> Project Setting and set Build System to Legacy Build System

![](https://cdn-images-1.medium.com/max/2272/1*n_Ydz7oLbwieJb5Ko-msRw.png)

## Where to go from here

In this post, we go through implementing QR code scanner feature with react-native-camera , how to debug issues with inconsistent between CI and local environment, how to clone repository to start from fresh, and how to deploy to Bitrise, Fabric and CircleCI. The key lesson for me is to track problem one after another, and never track generated or downloadable content in git, let the package manager handle that. We should also use more scripting to automate boring tasks and utilise node packages in scripting.

Original post https://medium.com/react-native-training/fixing-react-native-issues-and-happy-deploy-to-bitrise-fabric-circleci-44da4ab1487b