---
title: "How to build and deploy React Native applications with Fastlane"
url: "https://www.sparkfabrik.com/en/engineering/fastlane-build/"
lang: "en"
type: "engineering-post"
date: "2022-03-03"
lastmod: "2022-03-03"
author: "Edoardo Dusi"
description: "A detailed look at our internal Fastlane setup, operating on cross-platform React Native projects as a team, integrating the CI/CD environment and CLI operations."
tags: ["react native","fastlane"]
image: "https://www.sparkfabrik.com/images/og/engineering-fastlane-build-en.webp"
schema: {"@context":"https://schema.org","@type":"BlogPosting","author":{"@type":"Person","name":"Edoardo Dusi"},"dateModified":"2022-03-03T09:00:00+00:00","datePublished":"2022-03-03T09:00:00+00:00","description":"A detailed look at our internal Fastlane setup, operating on cross-platform React Native projects as a team, integrating the CI/CD environment and CLI operations.","headline":"How to build and deploy React Native applications with Fastlane","image":"https://www.sparkfabrik.com/images/og/engineering-fastlane-build-en.webp","inLanguage":"en","publisher":{"@type":"Organization","name":"SparkFabrik","url":"https://www.sparkfabrik.com"},"url":"https://www.sparkfabrik.com/en/engineering/fastlane-build/"}
---

# How to build and deploy React Native applications with Fastlane

A detailed look at our internal Fastlane setup, operating on cross-platform React Native projects as a team, integrating the CI/CD environment and CLI operations.

- Author: [Edoardo Dusi](https://www.sparkfabrik.com/en/engineering/authors/edo/)
- Published: 2022-03-03
- Topic: [Software development](https://www.sparkfabrik.com/en/engineering/families/dev/)
- Tags: react native, fastlane

## Introduction

**React Native** is a powerful framework for building mobile, desktop and even web applications, and with its CLI you can run a development version of a mobile app in an emulator or connected physical device almost with zero conf. Unfortunately, when you want to build and deploy for production, deal with code signing, certificates and keys, and share the development process across your team, it doesn’t offer a solution out of the box, and you still need to use platform-specific IDE like **Xcode** and **Android Studio.**

But there’s an open source tool that fills this gap and provides a complete automation for the whole process, starting from the development build to the signed production bundle: **Fastlane**.

In this post I will show you our internal **Fastlane** setup that lets us operate on cross-platform React Native projects as a team, integrate the CI/CD with **GitHub Actions**, and do almost everything from the CLI.

Note: this setup is tested on React Native projects, but should be easily adaptable to any mobile framework or native projects.

Let’s start.

## Installation

So first of all if you want to be cross-platform and build for iOS and Android, you’re gonna need a Mac so this post assumes that you are on a **macOS** and that you have everything set-up for a manual build with **Xcode** and **Android Studio**.

The key tool here is `fastlane` so my advice is to read the docs for a complete setup guide ([https://docs.fastlane.tools/](https://docs.fastlane.tools/)) but in short you need to install Xcode command-line tools and the fastlane tool.

```bash
> xcode-select --install
```

```bash
> brew install fastlane
```

And then cd to your project root and run:

```bash
> fastlane init
```

This will create a `fastlane` folder with an initial `Fastfile`, which is the config file for the tool.

## Environment setup

Since you want to build across different local machines and CI environments, you need to setup a `.env` file that `fastlane` is able to read ([https://docs.fastlane.tools/advanced/other/](https://docs.fastlane.tools/advanced/other/))

Here’s the template for a `.env` file that you should completely fill with your project’s specs.

```bash
KEYCHAIN_NAME=***.keychain
KEYCHAIN_PASSWORD=
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD=
EXPORT_METHOD=
IOS_PROJECT_PATH=
WORKSPACE_PATH=
APP_ID=
APPSTORE_USERNAME=
TEAM_ID=
TEAM_NAME=
OUTPUT_PATH=
SCHEME=
BUILD_PODS=true
PODS_PATH=ios/Podfile
UPDATE_TARGETS=
MATCH_PASSWORD=
MATCH_GIT_URL=
MATCH_GIT_BASIC_AUTHORIZATION=
MATCH_BUILD_TYPE=development
ANDROID_PROJECT_PATH=android
KEYSTORE_PATH=
KEYSTORE_PASSWORD=
KEYSTORE_ALIAS=
```

Each variable should be pretty self-explanatory, for some of them we’ll see more details later.

**Don’t commit the .env file!** You can commit the template, but this file contains secrets that should be handled with your CI secrets management.

## iOS

### Storing and sharing certificates

In order to create a signed build for iOS you need to use your Apple certificates, but you don’t want to create them manually and store them only in your local machine, nor you want to use Xcode’s automatic signing because this would mean that you cannot share them with your team or in CI, so you’ll use a powerful tool provided by fastlane that creates and sync your Apple certificates across builds: `match`.

If you read the docs you can see that `match` supports **Git repo**, **Google Cloud** or **Amazon S3** for storing the certificates and identities. For this post you will use **Git**.

Initialize match with:

```bash
> fastlane match init
```

This will create a `Matchfile` under your `fastlane` folder, but you can overwrite it with this one:

```bash
git_url(ENV["MATCH_GIT_URL"])

storage_mode("git")

type(ENV["MATCH_BUILD_TYPE"]) # The default type, can be: appstore, adhoc, enterprise or development

app_identifier(ENV["APP_ID"])
username(ENV["APPSTORE_USERNAME"]) # Your Apple Developer Portal username

keychain_name(ENV["KEYCHAIN_NAME"])
keychain_password(ENV["KEYCHAIN_PASSWORD"])

team_id(ENV["TEAM_ID"])

# For all available options run `fastlane match --help`
# Remove the # in the beginning of the line to enable the other options

# The docs are available on https://docs.fastlane.tools/actions/match
```

Follow these steps:

- Create a new keychain and fill the `.env` file with keychain name and password
- Create a private remote git repo (i.e. in **GitHub**)
- Create a private token and save base64 encrypted (`echo -n user:token | base64`) username:token in env file as `MATCH_GIT_BASIC_AUTHORIZATION`
- Configure env file with the URL of the git repo in `MATCH_GIT_URL`
- Ensure the env file is filled in every variable regarding app name, apple account, team id, passwords
    - *Note: `FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD` is required only if you want to push to the AppStore, follow this guide to create it [https://support.apple.com/en-us/HT204397](https://support.apple.com/en-us/HT204397)*
- From your project root, run `fastlane match`

Now you should have new certificates and provisioning profiles created in your Apple developer account and stored in your private git repo by `match`. You are now ready to build your app with `fastlane`.

### Setup the Fastfile

The `Fastfile` is a ruby file containing `fastlane` tasks (called `lanes`) that you can run individually.

Your first task should be a development build that produces a `.ipa` file that you can share with your team, or upload to a service like **Browserstack App Live**.

Something like this:

```ruby
default_platform(:ios)

platform :ios do
  before_all do
    unlock_keychain(
      path: ENV['KEYCHAIN_NAME'],
      password: ENV['KEYCHAIN_PASSWORD']
    )

    if ENV["BUILD_PODS"] == 'true'
      cocoapods(
        podfile: ENV["PODS_PATH"],
        use_bundle_exec: false
      )
    end
  end

	desc "Export ipa"
  lane :export_ipa do
    use_workspace = !ENV['WORKSPACE_PATH'].empty?

    match(type: "development")

    build_app(
      workspace: use_workspace ? ENV['WORKSPACE_PATH'] : nil,
      project: !use_workspace ? ENV['IOS_PROJECT_PATH'] : nil,
      configuration: ENV['CONFIGURATION'],
      scheme: ENV['SCHEME'],
      output_directory: File.dirname(ENV['OUTPUT_PATH']),
      output_name: File.basename(ENV['OUTPUT_PATH']),
      clean: true,
      export_method: ENV['EXPORT_METHOD'],
      export_team_id: ENV['TEAM_ID'],
    )
  end
end
```

You see that we are using a lot of our env variables here, so this is totally dependent on the environment. Let’s break it down to steps.

- First we configure the default platform and the lane platform
- In a `before_all` block, so before for each run, we unlock the keychain and if our env tells us to `BUILD_PODS`, then we use the fastlane command `cocoapods` to basically run `pod install`
- We create the lane `export_ipa`.
- `use_workspace` is a block variable that will be used later
- Then we tell `match` to set the build type to `development`, so it will use our development profile
- The last step is the actual build command

### Building locally with fastlane

You are ready for your first build. Run:

```bash
> fastlane export_ipa
```

The build process now starts and you will see tons of logs in your command-line. Hopefully no errors 🙂

And finally...

```bash
...
*fastlane.tools finished successfully* 🎉
```

You should find your `.ipa` file in your project root!

### Uploading a signed production build to TestFlight

So now that you have a working development build command, you can also runa production, signed build that you can upload to the appstore, test with **TestFlight** and then submit for review and publishing, all without even opening **Xcode** and from whatever local machine.

In order to do this you must simply fill the `FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD` with the one generated from your Apple account page as we said in a note before. 

Then, you need the appropriate lane in your `Fastfile`, so add this one to the `platform :ios` section:

```ruby
desc "Upload to TestFlight"
lane :upload_testflight do
  use_workspace = !ENV['WORKSPACE_PATH'].empty?

  match(type: "appstore")

  increment_build_number(
    xcodeproj: ENV['IOS_PROJECT_PATH'],
    build_number: (latest_testflight_build_number(
      app_identifier: ENV["APP_ID"],
      username: ENV["APPSTORE_USERNAME"],
      team_name: ENV["TEAM_NAME"],
    ) + 1).to_s,
  )

  build_app(
    workspace: use_workspace ? ENV['WORKSPACE_PATH'] : nil,
    project: !use_workspace ? ENV['IOS_PROJECT_PATH'] : nil,
    configuration: "Release",
    scheme: ENV['SCHEME'],
    output_directory: File.dirname(ENV['OUTPUT_PATH']),
    output_name: File.basename(ENV['OUTPUT_PATH']),
    clean: true,
    include_bitcode: true,
    export_method: "app-store",
    export_team_id: ENV['TEAM_ID'],
  )

  upload_to_testflight(
    ipa: File.join(File.dirname(ENV['OUTPUT_PATH']), File.basename(ENV['OUTPUT_PATH'])),
    username: ENV['APPSTORE_USERNAME'],
    app_identifier: ENV['APP_ID'],
    team_name: ENV['TEAM_NAME'],
    skip_waiting_for_build_processing: true,
  )
end
```

You can spot some differences with the development build.

- The `type` in `match` is now `appstore`, so **Match** will use the production profile and certs
- We added `increment_build_number` that basically fetches the latest build number from TestFlight and then increments it by 1
    - *Note: if `fastlane` complains that it cannot auto-increment the build number, follow this guide to setup Apple Generic versioning system: [https://developer.apple.com/library/archive/qa/qa1827/_index.html](https://developer.apple.com/library/archive/qa/qa1827/_index.html)*
- We then build and sign using `build_app` as before but with a `Release` configuration and `app-store` export method
- Finally we upload the build using `upload_to_testflight`
    - *Note: `skip_waiting_for_build_processing` means that `fastlane` will end right after the upload, if you don’t set it or set it to `false` the process will wait until the build is completely processed on TestFlight*

And that’s it, you can simply run

```bash
> fastlane upload_testflight
```

and your app should be uploaded to the App Store!

### Creating a new release on the App Store

**Fastlane** is a really powerful tool. It even allows you to completely control the review and publish process of your app completely from the command line.

This is not the purpose of this blog post, but I want to share a `lane` that creates a new release for your app on App Store, that you can use as a starting point to craft your own:

```ruby
desc "Release ipa"
lane :release do
  deliver(
    app_identifier: ENV["APP_ID"],
    username: ENV["APPSTORE_USERNAME"],
    team_name: ENV["TEAM_NAME"],
    build_number: (latest_testflight_build_number(
      app_identifier: ENV["APP_ID"],
      username: ENV["APPSTORE_USERNAME"],
      team_name: ENV["TEAM_NAME"],
    )).to_s,
    precheck_include_in_app_purchases: false,
    submit_for_review: false,
    force: true, # Skip HTMl report verification
    skip_screenshots: true,
    skip_binary_upload: true,
    submit_for_review: true,
    # Comes from https://github.com/fastlane/fastlane/issues/5542#issuecomment-254201994
    submission_information: {
      add_id_info_serves_ads: false,
      add_id_info_tracks_action: false,
      add_id_info_tracks_install: false,
      add_id_info_uses_idfa: false,
      content_rights_has_rights: true,
      content_rights_contains_third_party_content: true,
      export_compliance_platform: 'ios',
      export_compliance_compliance_required: false,
      export_compliance_encryption_updated: false,
      export_compliance_app_type: nil,
      export_compliance_uses_encryption: false,
      export_compliance_is_exempt: false,
      export_compliance_contains_third_party_cryptography: false,
      export_compliance_contains_proprietary_cryptography: false,
      export_compliance_available_on_french_store: false
    },
  )
end
```

Then running

```ruby
> fastlane release
```

should create a new release from your latest uploaded build and submit it for review.

As you can see, you can configure pretty much any aspect of the submission process. You can even add screenshots and descriptions!

### **Building remotely in GitHub CI**

So now you have a working local build, and since you have everything configured as environment variables and command-line tools you should be able to implement the same build using your preferred CI/CD system.

But if you are using **GitHub**, you can use an **Action** we created that let’s you easily and securely build remotely. It even uploads your build to **Browserstack App Live** if you want!

Here you can find our action: [https://github.com/sparkfabrik/ios-build-action](https://github.com/sparkfabrik/ios-build-action)

So you can simply create a new action template and `use` this, but this needs a couple more steps because you need to download the **Match** certificates, decrypt them and upload them as secrets to your **GitHub** project.

Follow these steps:

- Add `sparkfabrik/ios-build-action` to your GitHub Action, example:
    
    ```yaml
    - name: Build and deploy app
      uses: sparkfabrik/ios-build-action@v1.2.0
      with:
        project-path: ${{ secrets.PROJECT_PATH }}
        p12-key-base64: ${{ secrets.P12_KEY_BASE64 }}
        p12-cer-base64: ${{ secrets.P12_CER_BASE64 }}
        mobileprovision-base64: ${{ secrets.MOBILEPROVISION_BASE64 }}
        team-id: ${{ secrets.TEAM_ID }}
        workspace-path: ${{ secrets.WORKSPACE_PATH }}
        export-method: ${{ secrets.EXPORT_METHOD }}
        output-path: ${{ github.sha }}.ipa
        configuration: Debug
        update-targets: |
          MyApp
          YourApp
        disable-targets: App
        scheme: MyApp
        build-pods: true
        pods-path: 'ios/Podfile'
        browserstack-upload: true
        browserstack-username: ${{ secrets.BROWSERSTACK_USERNAME }}
        browserstack-access-key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
    ```
    
- Add the following secrets to GitHub (just copy / paste from your env file)
    - `PROJECT_PATH` (in env file it's `IOS_PROJECT_PATH`)
    - `TEAM_ID`
    - `WORKSPACE_PATH`
    - `EXPORT_METHOD`
    - `CERTIFICATE_PASSWORD` (in env file it's `MATCH_PASSWORD`)
    - `CODE_SIGNING_IDENTITY` (should be `iPhone Developer`)
    - *Optional*:  `BROWSERSTACK_USERNAME` and `BROWSERSTACK_ACCESS_KEY` (only if you want to upload to BrowserStack)
- Clone locally the private git repo where you stored the **Match** certificates
- Decrypt the `.cer` and `.p12` files in `/certs/(development|appstore)/` and `.mobileprovision` in `/profiles/(development|appstore)/` with the following command:
    - `openssl aes-256-cbc -k $MATCH_PASSWORD -in $YOURCERT.cer -out ios-build.cer -a -d`
    - `openssl aes-256-cbc -k $MATCH_PASSWORD -in $YOURCERT.p12 -out ios-build.p12 -a -d`
    - `openssl aes-256-cbc -k $MATCH_PASSWORD -in $YOURCERT.mobileprovision -out ios-build.mobileprovision -a -d`
    - $MATCH_PASSWORD is the same password you configured in env file
- Copy and paste the .p12, .cer and .mobileprovision files in base64
in github in the corresponding GitHub secrets (examples using `pbcopy`):
    - `P12_KEY_BASE64`: `base64 ios-build.p12 | pbcopy`
    - `P12_CER_BASE64`: `base64 ios-build.cer | pbcopy`
    - `MOBILEPROVISION_BASE64`: `base64 ios-build.mobileprovision | pbcopy`
    

That’s it for the **iOS** build, let’s move to the **Android** one which is waaaay easier 😀

## Android

Android build is easier because `gradle` is already a powerful tool for making command-line builds, so `fastlane` can basically act as a wrapper. **React Native** comes with a pre-configured gradle file that just works and produces development and production builds. 

So you only need to wrap `gradle` tasks in `fastlane` lanes, providing the correct configurations, and sign the app with the new app signing system that Android offers.

### Build unsigned production APK

For a development, unsigned build, you just need to fill the env variable `ANDROID_PROJECT_PATH` (in React Native this is `android`) and add this lane to your `Fastfile`:

```ruby
platform :android do
  desc "Assemble release APK"
  lane :assemble do
    gradle(
      task: "clean assembleRelease",
      project_dir: ENV["ANDROID_PROJECT_PATH"],
      flags: "--no-daemon",
    )

    APK_LOCATION = "#{lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]}"
    puts "APK location: #{APK_LOCATION}"
  end
end
```

We are using the `gradle` command, telling gradle to execute the `clean` task (so we have a clean build folder) and then the `assembleRelease` task, which produces an unsigned production build as an APK file. Then we print (using `puts`) in console the location of that file.

Of course you need to have `gradle` executable in your command-line environment, as long ad `Java` and everything Android needs, but if you build with React Native or other frameworks at this point you should already have everything set-up.

Now, you can just launch:

```ruby
> fastlane android assemble
```

And have your unsigned APK (the file location is printed before the final `fastlane summary` table) ready to be tested on a local device or uploaded to **BrowserStack**.

Note that here we assume that `ios` is the default platform, so in order to run `assemble` you must first specify the correct platform, which is `android`. 

### Build signed production bundle

In order to produce the signed bundle (`.aab`), first you need to prepare your app for signing, following the official guide here: [https://developer.android.com/studio/publish/app-signing](https://developer.android.com/studio/publish/app-signing)

When you have every file, save the `keystore` file, `pepk` key, `pem` certificate and credentials (alias, passwords) to a local secure folder, and upload it to a safe location (eg. private git repo, gcloud bucket, ...). Unfortunately, **Fastlane** doesn’t provide a tool like **Match** for managing Android keys so you need to store and share them on your own. 

Then you need to fill the following vars in your `.env` file:

```bash
KEYSTORE_PATH= (path to the .jks file)
KEYSTORE_PASSWORD= (the keystore password)
KEYSTORE_ALIAS= (the keystore alias)
```

The `lane` for your production build is this:

```ruby
lane :bundle_production do
	increment_version_code()
	
	gradle(
	  task: "clean bundleRelease",
	  project_dir: ENV["ANDROID_PROJECT_PATH"],
	  flags: "--no-daemon",
	  properties: {
	    "android.injected.signing.store.file" => File.join(Dir.pwd, "..", ENV["KEYSTORE_PATH"]),
	    "android.injected.signing.store.password" => ENV["KEYSTORE_PASSWORD"],
	    "android.injected.signing.key.alias" => ENV["KEYSTORE_ALIAS"],
	    "android.injected.signing.key.password" => ENV["KEYSTORE_PASSWORD"],
	  }
	)
	
	AAB_LOCATION = "#{lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH]}"
	puts "AAB location: #{AAB_LOCATION}"
end
```

First, we are incrementing the build version code using `increment_version_code`. This is not a command provided by **Fastlane**, it’s a plugin that you need to add to your fastlane installation, and that’s as simple as running:

```bash
> fastlane add_plugin increment_version_code
```

This command will create a `Pluginfile` in your `fastlane` folder with the added plugin. You can commit this file as **Fastlane** will check and install plugins from this file at every run.

Then, the lane calls `gradle` with tasks `clean` and `bundleRelease`, and this time we also want to sign the build so we pass `properties` using our environment variables pointing to the keystore.

Then, run the command

```bash
> fastlane android bundle_production
```

And the resulting AAB file path is printed before the final `fastlane summary` table.

This is the signed production buld, so you can upload this file to the Play Store and publish it. We do this manually at the moment, but there’s a fastlane action to upload directly to the store: `upload_to_play_store`. You can find more informations in the docs [https://docs.fastlane.tools/actions/upload_to_play_store/](https://docs.fastlane.tools/actions/upload_to_play_store/)

This post doesn’t cover it now, but maybe it will in the future, tell us if you are interested 😃

### **Building remotely in GitHub CI**

Good news: we have a **GitHub** action for Android too! Unfortunately it’s a lot simpler than our **iOS** action, because, again, we followed our needs, but you can contribute to it or open an issue and discuss it! Here’s the repo [https://github.com/sparkfabrik/android-build-action](https://github.com/sparkfabrik/android-build-action)

Using it in your action is just:

```yaml
- uses: sparkfabrik/android-build-action@v1.0.0
  with:
    project-path: android
    output-path: my-app.apk
    browserstack-upload: true
    browserstack-username: ${{ secrets.BROWSERSTACK_USERNAME }}
    browserstack-access-key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
```

Again `browserstack` is optional here, the produced APK is unsigned and you can then download it as an Artifact.

## Conclusions

**Fastlane** is a fantastic tool that lets you manage an entire build process without ever having to open an IDE, including signing and uploading to the app stores. Integrating all this to your local build is straightforward, and even in your CI/CD environment.

And there’s even more: for example there are actions in Fastlane to manage the git repo (like `git_commit`, `add_git_tag` or `push_to_git_remote`) or run tests, and many more, you can take full control over the build and release process of your app from a command-line, automate everything, share across the team and do real **[GitOps](https://blog.sparkfabrik.com/en/gitops-and-kubernetes)** 😃

I hope this was a clear and exhaustive post, configuring **Fastlane** may seem confusing at the beginning but once you start to master some of its features you realize how much power you have at your fingers!

Uh, I almost forgot, here’s the complete `Fastfile`:

```ruby
default_platform(:ios)

platform :ios do
  before_all do
    unlock_keychain(
      path: ENV['KEYCHAIN_NAME'],
      password: ENV['KEYCHAIN_PASSWORD']
    )

    if ENV["BUILD_PODS"] == 'true'
      cocoapods(
        podfile: ENV["PODS_PATH"],
        use_bundle_exec: false
      )
    end
  end

  desc "Export ipa"
  lane :export_ipa do
    use_workspace = !ENV['WORKSPACE_PATH'].empty?

    match(type: "development")

    build_app(
      workspace: use_workspace ? ENV['WORKSPACE_PATH'] : nil,
      project: !use_workspace ? ENV['IOS_PROJECT_PATH'] : nil,
      configuration: ENV['CONFIGURATION'],
      scheme: ENV['SCHEME'],
      output_directory: File.dirname(ENV['OUTPUT_PATH']),
      output_name: File.basename(ENV['OUTPUT_PATH']),
      clean: true,
      export_method: ENV['EXPORT_METHOD'],
      export_team_id: ENV['TEAM_ID'],
    )
  end

  desc "Upload to TestFlight"
  lane :upload_testflight do
    use_workspace = !ENV['WORKSPACE_PATH'].empty?

    match(type: "appstore")

    increment_build_number(
      xcodeproj: ENV['IOS_PROJECT_PATH'],
      build_number: (latest_testflight_build_number(
        app_identifier: ENV["APP_ID"],
        username: ENV["APPSTORE_USERNAME"],
        team_name: ENV["TEAM_NAME"],
      ) + 1).to_s,
    )

    build_app(
      workspace: use_workspace ? ENV['WORKSPACE_PATH'] : nil,
      project: !use_workspace ? ENV['IOS_PROJECT_PATH'] : nil,
      configuration: "Release",
      scheme: ENV['SCHEME'],
      output_directory: File.dirname(ENV['OUTPUT_PATH']),
      output_name: File.basename(ENV['OUTPUT_PATH']),
      clean: true,
      include_bitcode: true,
      export_method: "app-store",
      export_team_id: ENV['TEAM_ID'],
    )

    upload_to_testflight(
      ipa: File.join(File.dirname(ENV['OUTPUT_PATH']), File.basename(ENV['OUTPUT_PATH'])),
      username: ENV['APPSTORE_USERNAME'],
      app_identifier: ENV['APP_ID'],
      team_name: ENV['TEAM_NAME'],
      skip_waiting_for_build_processing: true,
    )
  end

  desc "Release ipa"
  lane :release do
    deliver(
      app_identifier: ENV["APP_ID"],
      username: ENV["APPSTORE_USERNAME"],
      team_name: ENV["TEAM_NAME"],
      build_number: (latest_testflight_build_number(
        app_identifier: ENV["APP_ID"],
        username: ENV["APPSTORE_USERNAME"],
        team_name: ENV["TEAM_NAME"],
      )).to_s,
      precheck_include_in_app_purchases: false,
      submit_for_review: false,
      force: true, # Skip HTMl report verification
      skip_screenshots: true,
      skip_binary_upload: true,
      submit_for_review: true,
      # Comes from https://github.com/fastlane/fastlane/issues/5542#issuecomment-254201994
      submission_information: {
        add_id_info_serves_ads: false,
        add_id_info_tracks_action: false,
        add_id_info_tracks_install: false,
        add_id_info_uses_idfa: false,
        content_rights_has_rights: true,
        content_rights_contains_third_party_content: true,
        export_compliance_platform: 'ios',
        export_compliance_compliance_required: false,
        export_compliance_encryption_updated: false,
        export_compliance_app_type: nil,
        export_compliance_uses_encryption: false,
        export_compliance_is_exempt: false,
        export_compliance_contains_third_party_cryptography: false,
        export_compliance_contains_proprietary_cryptography: false,
        export_compliance_available_on_french_store: false
      },
    )
  end
end

platform :android do
  desc "Assemble release APK"
  lane :assemble do
    gradle(
      task: "clean assembleRelease",
      project_dir: ENV["ANDROID_PROJECT_PATH"],
      flags: "--no-daemon",
    )

    APK_LOCATION = "#{lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]}"
    puts "APK location: #{APK_LOCATION}"
  end

  lane :bundle_production do
    increment_version_code()

    gradle(
      task: "clean bundleRelease",
      project_dir: ENV["ANDROID_PROJECT_PATH"],
      flags: "--no-daemon",
      properties: {
        "android.injected.signing.store.file" => File.join(Dir.pwd, "..", ENV["KEYSTORE_PATH"]),
        "android.injected.signing.store.password" => ENV["KEYSTORE_PASSWORD"],
        "android.injected.signing.key.alias" => ENV["KEYSTORE_ALIAS"],
        "android.injected.signing.key.password" => ENV["KEYSTORE_PASSWORD"],
      }
    )

    AAB_LOCATION = "#{lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH]}"
    puts "AAB location: #{AAB_LOCATION}"
  end
end
```

---

This is the Markdown version of an Engineering page of SparkFabrik, for AI agents and crawlers. The full page is at https://www.sparkfabrik.com/en/engineering/fastlane-build/.
