A React Native App from scratch to TestFlight with Expo Bare Workflow

Expo has done great to help developer creating native apps with react-native.

The Expo Managed Workflow is making it really easy to start a project and getting a production build .ipa or .apk file, but it has some downsides, and especially one : some libs created for React-Native are not available directly through Expo. That is the case of MapBox, which is why I had to have a look at Expo Bare Workflow, a workflow that still give the access to some good features of Expo if you wanted too.

Let’s go through the whole process, from start to deploy.

Config

  • Xcode 10.2.1
  • expo-cli 2.18.3

Create a Expo Bare Workflow project

# If you don’t have expo-cli yet, get it
npm i -g expo-cli
# If you don’t have react-native-cli yet, get it
npm i -g react-native-cli
# This is a shortcut to skip the UI for picking the template
expo init --template bare-minimum

Onve you entered the name of your app, Expo ask you if you prefer to use yarn to install dependencies. Do as you prefer, I chose yes, so you'll see some yarn in the next steps.

I called my app testmapbox so let’s jump in it:

cd testmapbox

Install React-Native Mapbox SDK

yarn add @react-native-mapbox-gl/maps

This will be our only dependency for our demo.

iOS is working with PodFiles. Don’t ask me what is is exactly, I didn’t search about it yet (but I would be glad that somebody tells a bit more about that in the comments, I can then use it here instead), but what I know is that for every dependency, we need to specify iOS where to find the files it needs to make it work. The Podfile looks like a dependency manager summary for iOS, like the package.jsonis for a javascript project.
Anyway, so jump in /ios/Podfile file, and following the README again, you need to add a line for MapBox as followed:

platform :ios, ‘10.0’
require_relative ‘../node_modules/react-native-unimodules/cocoapods’
target ‘testmapbox’ do
# Pods for testmapbox
pod ‘React’, :path => ‘../node_modules/react-native’, :subspecs => [
‘Core’,
‘CxxBridge’,
‘DevSupport’,
‘RCTActionSheet’,
‘RCTAnimation’,
‘RCTBlob’,
‘RCTGeolocation’,
‘RCTImage’,
‘RCTLinkingIOS’,
‘RCTNetwork’,
‘RCTSettings’,
‘RCTText’,
‘RCTVibration’,
‘RCTWebSocket’,
]
pod ‘yoga’, :path => ‘../node_modules/react-native/ReactCommon/yoga’pod ‘DoubleConversion’, :podspec => ‘../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec’
pod ‘glog’, :podspec => ‘../node_modules/react-native/third-party-podspecs/glog.podspec’
pod ‘Folly’, :podspec => ‘../node_modules/react-native/third-party-podspecs/Folly.podspec’
#### ADD THIS NEW LINE HERE
pod 'react-native-mapbox-gl', :path => '../node_modules/@react-native-mapbox-gl/maps'
use_unimodules!target ‘testmapboxTests’ do
inherit! :search_paths
end
end

It is described at the end of the expo init command what to do next : before running your app on iOS, make sure you have CocoaPods installed, and when that is done, let's initialise the project for iOS with what we just added to the Podfile

cd ios
pod install

Then you can run the project

# Back in testmapbox folder
cd ..
yarn ios
# If you want to select a specific phone from the simulator, add the # --simulator flag with the phone you want, for example
yarn ios --simulator="iPhone 5"

So what it should do is:

  • Open a iOS simulator with the default simulator or the one you chose
  • Open another terminal window saying Metro Bundler running on port 8081 . Remember the port number, you'll need it in a few seconds.
  • Terminate the script in the original terminal window, with something like that
Installing build/Build/Products/Debug-iphonesimulator/testmapbox.app
Launching org.reactjs.native.example.testmapbox
org.reactjs.native.example.testmapbox: 51828
✨ Done in 99.72s.

And in the iOS simulator, you should have

Welcome message of React Native in iOS simulator

Install React Native Debugger

open ‘rndebugger://set-debugger-loc?port=8081’

with the same port number I told you to remember just before. Once it's done, in your simulator you type Cmd + D and click on Start Remote JS Debugging . You can also click enable Hot Reload (automatic reload of your JS script when you make changes, and keeping the state of the app) or Live Reload (reloading the app every time the JS script changes) if you want.

Set-up MapBox map

This blog post helped me to set-up the map correctly, I won't go in so much details about it, but here is how your code should look like in your App.js file

import React from 'react';
import { View } from 'react-native';
import MapboxGL from '@react-native-mapbox-gl/maps';
MapboxGL.setAccessToken(YOUR_MAPBOX_API_TOKEN);const rouen = [1.100030, 49.442375]const zoom = {
country: 5,
region: 8,
bigCity: 10,
city: 12,
quarter: 14,
zone: 16,
precise: 18,
house: 20
}
const App = () =>
<View style={{ flex: 1 }}>
<MapboxGL.MapView
style={{ flex: 1 }}
showUserLocation
styleURL="
mapbox://styles/mapbox/navigation-guidance-night-v2"
>
<MapboxGL.Camera
ref={(c) => this._camera = c}
defaultSettings={{
centerCoordinate: rouen,
zoomLevel: zoom.zone
}}
/>
<MapboxGL.UserLocation
visible
animated
/>
</MapboxGL.MapView>
</View>
export default App

That's it for the code, and this is how it should be in the simulator:

Crazy city…

Now you can checkout the MapBox documentation and start coding !

Get an Apple Developer Account

Apple Developer account details

Build on iOS

{
“scripts”: {
“android”: “react-native run-android”,
“ios”: “react-native run-ios”,
“start”: “react-native start”,
“build:ios”: “react-native bundle — entry-file index.js — platform ios — dev false — bundle-output ios/main.jsbundle — assets-dest ios”,
“test”: “jest”
},
“dependencies”: {
“@mapbox/react-native-mapbox-gl”: “https://github.com/nitaliano/react-native-mapbox-gl#master",
“react”: “16.6.3”,
“react-native”: “0.58.6”,
“react-native-unimodules”: “0.3.1”
},
“devDependencies”: {
“babel-jest”: “24.1.0”,
“jest”: “24.1.0”,
“metro-react-native-babel-preset”: “0.52.0”,
“react-test-renderer”: “16.6.3”
},
“jest”: {
“preset”: “react-native”
},
“private”: true
}

From your terminal, run

yarn build:ios

It should end up nicely with something like

Loading dependency graph, done.
bundle: Writing bundle output to: ios/main.jsbundle
bundle: Done writing bundle output
✨ Done in 27.10s.

Now let's jump into XCode.
Open the testmapbox.xcworkspace in Xcode, to have this project structure

Project Navigator in Xcode

Go to menu Product > Scheme > Edit scheme (or Cmd + <)

Just in case…

And change build configuration from Debug to Release

Now in the Build Settings, change the Dead Code Stripping option of Release to "No".

If you search for "Dead" on the top right of the center panel, it is easier to find

Be careful, you need to apply this setting to the proper target.

It should be applied to the main target, not the project

If your app is not using encryption, which is our case, you can also add an option to your Info.plist which will save you from providing an Export Compliance Information file everytime you upload an app.

Click on +, paste ITSAppUsesNonExemptEncryption, choose Boolean and set to No

Now you can click on the Build button (or Cmd + B), it should be successful !

Distribute the app using TestFlight

You need to set-up the General settings with a Bundle identifier (you choose whatever, but you never change it afterwards, choose it carefully), select a Team, set-up the App Icons and Launch Images.

This is where you set all this up

Choose a Generic iOS Device as a deployment target (top-left of the main window), and click on the menu Product > Archive.

Once it's done, open the Archive Organizer (or Alt + Maj + Cmd + O)

Then I tell you, it's the wild wild west because it's the first time I've done it and I don't know if I done it correctly (please tell me in the comments), but here is what I did…

So before clicking on Validate, you need to register the Bundle Id on your developer account. Be really careful to have exactly the same Bundle Id in your developer account and in your project. You also need to create a new App on your AppStoreConnect account. If you don't see the Bundle Id you just created, wait a little bit it will come.
Then click on Validate App, and accept the two next panels.

This does not seem to be a big deal anyway
This seems more big deal, but let's go for automatic manage signing… inch allah !

If you already had some signing certificates before, for instance some created with Expo(which is my case), I don't exactly know the proper way to do, so what I did is I revoked all the certificates already had in my Apple Developer account, and here is the next screen:

Good for me, the next steps are straight forwards, until the victory message appears

We won a battle, but not yet the war

Then you can click on Distribute, choose iOS App Store and the same steps as Validate will appear. That makes me think that the Validate step is unnecessary, because I also tried to click directly on Ditribute without clicking on Validate, and it worked too. Anyway, wait until the next victory message appears telling than the app has uploaded.

We won the war

You can go to your AppStore Connect account, click on MyApp > Your App, on TestFlight tab you'll see your app Processing then Testing, and the rest is quite straight forward.

That's it ! Next time, I will do the same for deploying on Android.

If you have some comments, please do and I will improve this blog post with your advices.

Cheers !