From react-native init to app stores real quick

From react-native init to stores real quick

  1. Our goals
  2. Prerequisite
    1. iOS
    2. Android
  3. Create your app
  4. Environments and variables
    1. Enable vendor name in Android
    2. React Native Config
  5. Configure the release
    1. Generate an Android Signing Key
    2. Configure the gradle build
    3. Configure the iOS build
    4. Change the public app name
  6. Test our setup
    1. Quick launch commands
  7. Store configuration
    1. Generate store icons
    2. Setting up a launch screen
  8. Build store release
    1. iOS release
    2. Android release
  9. Troubles?

Hi, I'm Thomas and I'm gonna show you how to build and release a React Native app to iOS and Android stores on macOS.

Our goals

What we aim to accomplish:

  • Automated build process that generates signed and ready-for-stores release builds.
  • Handling different environments (development, staging, production) with different domain-related variables (e.g. URL of the back-end API).

So let's get to it.

Prerequisite

iOS

You'll need a Apple ID and Xcode is installed on you mac.

Also ensure that you agreed to Apple's latest terms and conditions (just launch Xcode and click Agree).

Android

🔧 Get Android Studio with tools required by React Native

🔧 Launch Android Studio and install Android SDK and other required libraries React Native requires Android 6.0 and SDK 23.0.1

🔧 Configure your Android Home environment variable by adding the following lines in your bash profile file:

export ANDROID_HOME=${HOME}/Library/Android/sdk
export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools

Create your app

We won't be using the default react-native way that relies on Expo (a container to run your React-Native code in).

While Expo might be fine for experimenting and quickly bootstraping an App, it's not compatible with setting up a real release process as stated in our goals.

So instead, let's go to the Building Projects with Native Code section of the documentation:

🔧 Install the React Native cli tool:

npm install -g react-native-cli

🔧 Create your app and be careful to choose a name that contains no dots, spaces or other special characters.

react-native init AcmeApp
cd AcmeApp

Environments and variables

To build a release of the app, we need to give it a few pieces of information:

  • A unique identifier: com.acme.app
  • A version number: 1.0.0
  • A build number: 1
  • Any domain-releted variables we need.

Unique identifier

Note the format of the unique identifier: a reverse domain name, starting with com, then the vendor name and then the app name.

I strongly recommend that you follow this format and avoid any special character.

This identifier must not be already used on Google Play Store or Apple's App Store.

Once published, an app can't change its identifier, so choose a name that can live through the whole life of the app. Don't worry though, it's only a technical identifier, it will not be displayed to the public anywhere and you'll have a Display name for that and this one can change anytime you need.

Version Number

The version number must follow the semver format major.minor.patch.

Build Number

The build number is just an integer as simple as 1.

You'll increment build number every time you publish a release. You can't upload a release that has a build number inferior or equal to any build you ever uploaded for this app.

We might also need some domain-releated variables like the address of the API providing the data for the app.

Such variables will likely have different values in different environments.

For example, we want to call http://api.app.acme.test in development mode in the simulator, and https://api.app.acme.com in a production release for the stores.

Enable vendor name in Android

Before anything else, we need to configure Android to support the unique identifier we chose for the app.

The init script generated a unique identifier for Android based on the name you gave it (e.g. com.acmeapp for AcmeApp).

💡 You can see what name was generated by looking for the applicationId key in android/app/build.gradle.

If you need to change that unique identifier, do it now as described below:

🔧 In the /android folder, replace all occurrences of com.acmeapp by com.acme.app

🔧 Then change the directory structure with the following commands:

mkdir android/app/src/main/java/com/acme
mv android/app/src/main/java/com/acmeapp android/app/src/main/java/com/acme/app

💡 You need a folder level for each dot in the app identifier.

React Native Config

For setting and accessing the variables described above wherever we need it, I recommend using react-native-config

🔧 Install it with:

yarn add react-native-config
react-native link react-native-config

🔧 Then create an Env file for each environment in the root folder of the project, with the following suggested variables:

.env.development

APP_ID=com.acme.app
APP_ENV=development
APP_VERSION=0.1.0
APP_BUILD=1
API_HOST=http://api.app.acme.local

.env.staging

APP_ID=com.acme.app
APP_ENV=staging
APP_VERSION=0.1.0
APP_BUILD=1
API_HOST=http://api.app.acme.staging

.env.production

APP_ID=com.acme.app
APP_ENV=production
APP_VERSION=0.1.0
APP_BUILD=1
API_HOST=http://api.app.acme.com

Configure the release

Generate an Android Signing Key

Android requires that you sign APK before releasing them on the Play Store. To do so you will need to generate a personal signing key and to configure your Gradle build to use that key.

Edit your local gradle config file ~/.gradle/gradle.properties:

🔧 Fill it with the following lines:

MY_RELEASE_STORE_FILE=my_release.keystore
MY_RELEASE_KEY_ALIAS=my_release
MY_RELEASE_STORE_PASSWORD={Generate a 32 characters password}
MY_RELEASE_KEY_PASSWORD={Generate another 32 characters password}

💡 You'll need to generate 2 passwords

🔧 Now generate the keystore key file:

keytool -genkey -v -keystore ~/.gradle/my_release.keystore -alias my_release -keyalg RSA -keysize 2048 -validity 10000

🔧 Follow the instructions as below:

Entrez le mot de passe du fichier de clés: {MY_RELEASE_STORE_PASSWORD}
Ressaisissez le nouveau mot de passe: {MY_RELEASE_STORE_PASSWORD}
Quels sont vos nom et prénom ? Jane Doe
Quel est le nom de votre unité organisationnelle ? Lyon
Quel est le nom de votre entreprise ? élao
Quel est le nom de votre ville de résidence ? Lyon
Quel est le nom de votre état ou province ? Rhones-Alpes
Quel est le code pays à deux lettres pour cette unité ? FR
Est-ce CN=Jane Doe, OU=Lyon, O=élao, L=Lyon, ST=Rhones-Alpes, C=FR ? oui
Entrez le mot de passe de la clé pour <my_release> {MY_RELEASE_KEY_PASSWORD}
Ressaisissez le nouveau mot de passe : {MY_RELEASE_KEY_PASSWORD}

🔧 Finally, link the keystore to the project with a symbolic link:

ln -s ~/.gradle/my_release.keystore android/app/my_release.keystore

Configure the gradle build

Edit the android/app/build.gradle file and apply the following changes:

🔧 Insert the following line on line 2:

apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"

🔧 In the config block, replace the following lines:

defaultConfig {
-    versionCode 1
+    versionCode project.env.get("APP_BUILD") as Integer
-    versionName "1.0"
+    versionName project.env.get("APP_VERSION")
  // ...
}

🔧 Between the splits and buildTypes blocks (should be line 118) add the following block :

signingConfigs {
    release {
        storeFile file(MY_RELEASE_STORE_FILE)
        storePassword MY_RELEASE_STORE_PASSWORD
        keyAlias MY_RELEASE_KEY_ALIAS
        keyPassword MY_RELEASE_KEY_PASSWORD
    }
}

🔧 Then add the signingConfig property in the buildTypes > release section (should be line 131)

buildTypes {
    release {
        // ...
        signingConfig signingConfigs.release
    }
}

Configure the iOS build

Open your iOS project iOS/AcmeApp.xcodeproj with Xcode and select the root item AcmeApp in the file browser on the left.

General tabIn the

🔧 In the Identity section input the following values:

  • Bundle Identifier: Your app unique identifier com.acme.app (instead of org.reactjs.native.example.AcmeApp)
  • Version: __RN_CONFIG_APP_VERSION
  • Build: __RN_CONFIG_APP_BUILD

🔧 In the Signing section, select a Team

Build Settings tabIn the

🔧 In the All section:

  • Search for "preprocess"
  • Set Preprocess Info.plist File to Yes
  • Set Info.plist Preprocessor Prefix File to ${BUILD_DIR}/GeneratedInfoPlistDotEnv.h
  • Set Info.plist Other Preprocessor Flags to -traditional

💡 If you don't see those settings, verify that "All" is selected at the top (instead of "Basic").

Change the public app name

To change the name that will be displayed to the public on the stores:

On iOS

🔧 Open your project with XCode and set a Display Name in the General > Identify section.

On Android

🔧 Set the displayName property in the the app.json file at the root of the project.

Test our setup

Here's a simple homepage that displays the app version, build number, environment and platform. You can open your ./App.js file and replace its content with the following code:

import React, { Component } from 'react';
import { Platform, Text, View, StyleSheet } from 'react-native';
import Config from 'react-native-config';

export default class App extends Component {
  static styles = StyleSheet.create({
    container: {
      flex: 1,
      justifyContent: 'center',
      alignItems: 'center',
      backgroundColor: '#ffffff',
    },
    infos: {
      textAlign: 'center',
      color: '#333333',
    },
  });

  render() {
    const { styles } = App;
    const { APP_ENV, APP_VERSION, APP_BUILD } = Config;
    const { OS } = Platform;

    return (
      <View style={styles.container}>
        <Text style={styles.infos}>
          AcmeApp v{APP_VERSION} build {APP_BUILD}
        </Text>
        <Text style={styles.infos}>
          [{APP_ENV}] on {OS}
        </Text>
      </View>
    );
  }
}

Quick launch commands

Start the react-native packager manually : react-native start

Now a simple react-native run-ios should start your app in iOS simulator.

  • react-native run-ios --simulator 'iPhone SE' Run the app in the simulator on a specific device.
  • react-native run-ios --device Run the app on a real iPhone (that must be connected to the mac by USB).
  • react-native run-ios --device --configuration Release Run the app on a real iPhone as a release (fast and no debug, just like it will be in the store).
  • react-native run-android Run the app on any simulated or real android device found.
  • react-native run-android --variant=release Run the app on any simulated or real android device found, as a release.

What's more, we can now run the app in a certain environment by specifying the ENVFILE that React Native must use:

Develoment Staging Production
ENVFILE=.env.development react-native run-ios ENVFILE=.env.staging react-native run-ios ENVFILE=.env.production react-native run-ios

Store configuration

Generate store icons

Apple's App Store and the Google Play Store both require that you provide icons for your app in various formats.

Fortunately, there are a few online-services that will generate those icons for you from a high-res source icon.

I personally use https://makeappicon.com

  • Upload a high-res square icon (ideally 1536x1536).
  • Enter an email (you don't have to subscribe to the newsletter).
  • You will receive the icons by email.

Setup icons for iOS

🔧 Move the content of iOS in the following path in your project folder: iOS/AcmeApp/Images.xcassets/ (if asked, replace existing content with the new icons).

Setup icons for Android

🔧 Move the content of android in the following path in your projet folder: android/app/src/main/res (if asked, replace existing content with the new icons).

Setting up a launch screen

Generation

For splash screen, I use: http://apetools.webprofusion.com/tools/imagegorilla

  • Upload a high-res square splash screen (ideally 2048x2048).
  • Press Kapow.
  • Download the zip file.

🔧 Then place the generated launch screen in the same folder as the store icons described above.

Configuration

iOS

Open your project in XCode:

  • In the General tab, go to the App Icons and Launch Images section.
  • On the Launch Images Source line, click the User Asset Catalog button, then click migrate.
  • Once done, click the little grey arrow at the end of the App Icons Source line.

🔧 Click on the newly created LaunchImage catalog, and fill every required launch screen with the corresponding format from the generated image folder.

💡 If you're having trouble figuring out which format corresponds to which case, the alert tab on the left will tell you exactly what size is expected for each image.

🔧 Finally, go back in the General tab, in the App Icons and Launch Images section, do the following:

  • As Launch Images Source select LaunchImage.
  • Empty the Launch Screen File.

💡 You can now delete the LaunchScreen.xib in the AcmeApp folder.

Android

🔧 Create a android/app/src/main/res/drawable/splash_screen.xml file:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <bitmap android:gravity="center" android:src="@drawable/screen"/>
    </item>
</layer-list>

🔧 Create a android/app/src/main/res/values/colors.xml file:

<resources>
    <color name="primary">#ffffff</color>
</resources>

💡 Here #ffffff is there to set a white background to the app, feel free to replace it with the color of your choice.

🔧 Then edit android/app/src/main/res/values/styles.xml to add the following node in the style tag:

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
+    <item name="android:windowBackground">@drawable/splash_screen</item>
</style>

Build store release

iOS release

The following command will generate your iOS release:

ENVFILE=.env.production xcodebuild \
    -workspace ./iOS/AcmeApp.xcodeproj/project.xcworkspace \
    -scheme AcmeApp \
    -sdk iphoneos \
    -configuration AppStoreDistribution archive \
    -archivePath ./iOS/release/AcmeApp.xcarchive

The archive will be available at ./iOS/release/AcmeApp.xcarchive and you can open it in Xcode to build an IPA for development purpose or upload it to the App Store.

Android release

The following command will generate your Android release:

. ./.env.production && cd android && ./gradlew assembleRelease

The APK will be available at ./android/app/build/outputs/apk/app-release.apk and you can upload it directly to the Play Store.

Troubles?

Here's a working example of all we discussed above : https://github.com/Elao/AcmeApp

💡 Note the handy Makefile that hides all complex build and release commands behind simple make tasks like: make run android or make release-ios

Also don't hesitate to reach out for help in the comment or on social medias.