Deploy React Native app on  Playstore with Fastlane and Github Actions ( 1 / 2 )
Tags: reactnative, fastlane, android, playstore
Published: 2021-03-15 09:39:29

With this series of two posts, I’m going to detail how to setup a continuous deployment for an Android app made with React Native. (Bonus: it also works without React Native!).

I will use and configure Fastlane with Github Actions in order to deploy on Playstore at each pre-release or even each commit if you want. This post will be split into two parts:

  - Deploy an Android App Bundle on Playstore with Fastlane
  - Trigger Fastlane with Github Actions for uploading AAB (Android App Bundle) on Playstore.

In this post, I will use AAB but you can also use APK.

The source code of the application is available in this Github repository: https://github.com/antoinecellier/rn-fastlane-githubactions/

![Fastlane worflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fq05f42ic4q9ih6rv5qo.png)

###Setting up Fastlane on your machine

Fastlane is an open source platform aimed at simplifying Android and iOS deployment. It lets you automate every aspect of your development and release workflow. 

Your workflow will be defined by Ruby files which are called `Appfile` and `Fastfile`.
 
The `Appfile` stores useful information that are used across all Fastlane tools like your Apple ID or the application Bundle Identifier, to deploy your lanes faster and tailored on your project needs.

The `FastFile` contains the Lanes definition. The Lanes are functions that define actions to execute in a sequential way. For example: 

`update version of the app` -> `build the app` -> `deploy the app`

To install it on your machine, follow these steps in Fastlane documentation: https://docs.fastlane.tools/getting-started/android/setup/

To be sure that Fastlane is correctly installed, just use `fastlane --help` in the terminal:

![Check Fastlane installation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l89bu13q1qokgylmvpp2.png)

###Create a Google Developers service account

The Google Developers Services will be used to upload an Android App Bundle on Playstore.

Follow these steps in Fastlane documentation: https://docs.fastlane.tools/actions/upload_to_play_store/#setup

At the end of these steps, you will have a credentials json file looking like this:

```json
{
 "type": "...",
 "project_id": "...",
 "private_key_id": "...",
 "private_key": "...",
 "client_email": "...",
 "client_id": "...",
 "auth_uri": "...",
 "token_uri": "...",
 "auth_provider_x509_cert_url": "...",
 "client_x509_cert_url": "..."
}
```

###Generate a Keystore file in order to sign in the app
If you use Android App Bundles, you need to sign the app bundle before you upload it to the Play Console, and Play App Signing takes care of the rest.

Generate a release.keystore file. 4 mandatory elements are in this file:
- The file itself.
- The key alias (see the keytool command below).
- The keystore password.
- The key password.

```shell
$ keytool -genkey -v -keystore release.keystore -alias <your key alias> -keyalg RSA -keysize 2048 -validity 10000
```

For more information, go to https://developer.android.com/studio/publish/app-signing#generate-key 

Before continuing, make sure you have:
- A **functional Fastlane** on your machine.
- A **Google Developer service account** with your json credentials file.
- A **release.keystore** file generated.

### Setting up Fastlane
From now on, we will consider that the Android configuration files are generated by React Native init without changes.

Here is how to initialise a React Native app:
```shell
$ npx react-native init AwesomeProject
```
In the Android folder, run:
```shell
$ fastlane init
```

Prompt steps:
- Provide the package name for the application when asked (e.g. com.my.app).
- Press enter when asked for the path to your json secret file.
- Answer 'n' when asked if you plan on uploading info to Google Play via Fastlane (we can set this up later).

![Prompt steps for Fastlane init](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/407gu4dvr0etdushxwrk.png)

Fastlane folder has been created and contains two files:
- `Appfile` which defines global configuration information of the app.
- `FastFile` which defines lanes of Fastlane.

The `service_account.json` file of your Google Developer service account will be used in `AppFile`. Copy the json file inside the Fastlane folder and modify `android/fastlane/AppFile`:

```ruby
json_key_file("fastlane/service_account.json") 
package_name("com.my.app")
```

You can now test the connection to the Google Play Store:

```shell
$ fastlane run validate_play_store_json_key json_key:/path/to/your/file.json
```
![Playstore credentials validation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/awavjcjdml44cmy23ujs.png)

###Setting up fastlane plugins

Plugins allow reuse fonctions created by the community by installing them from the Fastlane client. 

You going to add Fastlane plugins for:

Retrieve the version number of the application in the package.json file that will define the version number of the Android App Bundle. For this,  we need to add the  load_json plugin:

```shell
$ fastlane add_plugin load_json
> Should fastlane modify the Gemfile 
	at path '/xxx/react-native-app/android/Gemfile' for you? (y/n) 
> y
```

To be uploaded on Playstore, each AAB or APK must have a unique version code. To set the version code of your app, install the `versioning_android` plugin:

```shell
$ fastlane add_plugin versioning_android
```

###Now let's configure the first lane in the `FastFile` file.

The first thing to do is to implement a function that generates a unique `versionCode` for the Android App Bundle. Put this function, that generate a new version code, in the upper part of the `android/fastlane/FastFile`:

```ruby
default_platform(:android)

def getVersionCode
  # Instead of managing the version code manually it is based on a timestamp in seconds
  # Any build done more recently is considered to be a higher version
  # versionCode increase every minute (so max 1 build per minute)
  # versionCode cannot be smaller than legacyVersionCode
  thirtySeptemberTwentyTwenty = 1601480940 / 60
  legacyVersionCode = 10902
  versionCode = legacyVersionCode + (Time.now.to_i / 60) - thirtySeptemberTwentyTwenty

  if versionCode > 2100000000
    raise "versionCode cannot be higher than 2100000000"
  end

  versionCode.floor()
end

...
```

And define the first lane that will use getVersionCode to update `android.defaultConfig.versionCode` in the `app/build.gradle` file:

```ruby
... 
 platform :android do 	
  desc "Increments internal build number tracking (different than version)" 
    lane :bump_build_number do 
     android_set_version_code( 
      version_code: getVersionCode() 
     ) 
 end 
...
```

Still in `android/fastlane/FastFile`, you can define the lane for building and uploading the Android App Bundle on Playstore.

```ruby
desc "Build and uploads the app to playstore for a internal testing release"
  lane :playstoreInternal do |options|
    # Retrieve version of my app in package.json (React Native case)
    package = load_json(json_path: "../package.json")

    # Clean build folder
    gradle(
      task: "clean"
    )

    # Bump android.defaultConfig.versionCode
    bump_build_number

    # Do the bundle of the application
    gradle(
      task: 'bundle',
      build_type: 'Release',
      properties: {
        "android.injected.signing.store.file" => Dir.pwd + "/release.keystore",
	"android.injected.signing.store.password" => options[:RELEASE_KEYSTORE_PASSWORD], # keystore password
        "android.injected.signing.key.alias" => options[:RELEASE_KEYSTORE_ALIAS], # alias
        "android.injected.signing.key.password" => options[:RELEASE_KEYSTORE_KEY_PASSWORD], # key password
        "vname" => package["version"]
      }
    )

    # Upload Android App Bundle to PlayStore like Internal testing Release
    upload_to_play_store(
      track: 'internal',
      release_status: 'draft', # <http://docs.fastlane.tools/actions/upload_to_play_store/#parameters>
      skip_upload_apk: true,
      version_name: package["version"]
    )
  end
```

Let’s look into details the lane:

- First we retrieve the application version from the `package.json` file
```ruby
desc "Build and uploads the app to playstore for a internal testing release"
lane :playstoreInternal do |options|
    package = load_json(json_path: "../package.json")
```
- Then clean up the build folder and use `bump_build_number` lane created above:
```ruby
gradle(
 task: "clean"
)

bump_build_number
```

- Finally, we define a gradle task to build the application in the app/build.gradle file: 
```ruby
gradle(
	task: 'bundle',
	build_type: 'Release',
	properties: {
         "android.injected.signing.store.file" => Dir.pwd + "/release.keystore",
	 "android.injected.signing.store.password" => options[:RELEASE_KEYSTORE_PASSWORD], # keystore password
         "android.injected.signing.key.alias" => options[:RELEASE_KEYSTORE_ALIAS], # alias
         "android.injected.signing.key.password" => options[:RELEASE_KEYSTORE_KEY_PASSWORD], # key password
         "vname" => package["version"]
       }
)
```

4 different properties are needed to allow gradle to sign your application:
- `android.injected.signing.store.file`: Path to the release.keystore file.
- `android.injected.signing.store.password`: Keystore password.
- `android.injected.signing.key.alias`: Key alias of the release.keystore file.
- `android.injected.signing.key.password`: key password.

Three of these properties are sensitive information, so they can be filled through the Fastlane parameters. For example:

```shell
$ fastlane playstoreInternal RELEASE_KEYSTORE_PASSWORD:**keystore password** 
```
At this step, you can check if the application is properly bundled. Copy your `release.keystore` file in the `android/fastlane` folder and run the command below with the passwords and the alias of your `release.keystore` file:

```shell
$ fastlane playstoreInternal 
>   RELEASE_KEYSTORE_PASSWORD:**mypassword** 
>		RELEASE_KEYSTORE_ALIAS:**myalias** 
>		RELEASE_KEYSTORE_KEY_PASSWORD:**mykeypassword**  
```

If you copy paste the entire file above, comment `upload_to_play_store` action in order to just test Gradle build.

![Test of the build release lane](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vq0290ybuwao8lju64qa.png)
The last step of the lane is to upload the aab on Playstore:

```ruby
upload_to_play_store(
  track: 'internal', # 'internal' for internal testing release
  release_status: 'draft', # http://docs.fastlane.tools/actions/upload_to_play_store/#parameters>
  skip_upload_apk: true, # if you want to upload apk, use skip_upload_aab instead
  version_name: package["version"]
)
```

It's time to test the whole workflow of your awesome lane:
```shell
$ fastlane playstoreInternal 
>   RELEASE_KEYSTORE_PASSWORD:**mypassword** 
>		RELEASE_KEYSTORE_ALIAS:**myalias** 
>		RELEASE_KEYSTORE_KEY_PASSWORD:**mykeypassword** 
```

![Test of the deploy release lane](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y1zpatiz1drwjzn12chk.png)

Let's check the result in the Google Play Console.
![AAB uploaded on Playstore](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h8w8tg4h6s1n1owfe43r.png)

If you have another way to upload your applications on Playstore or to improve this workflow, please feel free to share it with us.

Source code is available in this repository:
https://github.com/antoinecellier/rn-fastlane-githubactions

In the second part of this post we will see how to use Github Actions in order to upload an Android App Bundle on Playstore at each pre-release.

A big thank you to [@eric_briand](https://twitter.com/eric_briand), [@bpetetot](https://twitter.com/bpetetot) and [@tbetous](https://twitter.com/tbetous) for their proofreading.

Sources:
- https://developer.android.com/studio/publish/app-signing#generate-key
- https://docs.fastlane.tools/getting-started/android/setup/
- https://docs.gradle.org/current/userguide/userguide.html