WritingsThoughts on things

August 19, 2019 7 min read

Fastlane: Painless Mobile App Builds and Deployment

Getting from idea to released app is a long process. As developers, our focus should remain on code and user experience. Releasing a mobile app requires so much more, from screenshot generation to code signing to app store deployments, developing and releasing a mobile app can be a tedious process which Fastlane helps make easier.

Shipping ContainersShipping Containers
Shipping Containers

When I first got started with React Native, I hoped to focus on learning the intricacies of developing cross-platform apps using my existing knowledge of React and not deal with the headaches I had previously experienced around provisioning profiles, certificates and code signing. Not long into one of my first projects, I discovered fastlane, a way to automate the development and release process for mobile apps.

fastlane is the easiest way to automate beta deployments and releases for your iOS and Android apps. 🚀 It handles all tedious tasks, like generating screenshots, dealing with code signing, and releasing your application.

Currently, I'm using fastlane for code signing and deployment so that is what will be covered in this post but I encourage you to checkout fastlane's available actions to see what else it can automate for you.

Getting Started with Fastlane

First we need to get fastlane installed. However, we need to first install the latest Xcode command line tools if they are not already installed.

$ xcode-select --install

Next, let's install fastlane via homebrew.

$ brew cask install fastlane

Once installed, we need to create a fastlane folder inside our React Native project root. Next, create an empty file called Fastfile. Alternatively, there is a fastlane init command that can be used to handle some of the scaffolding. Our Fastfile will consist of all of our lanes which contain fastlane actions that execute synchronously to automate our processes.

fastlane_version '2.129.0'

before_all do
  ensure_git_branch
  ensure_git_status_clean
  git_pull
end

platform :ios do
  # iOS specific lanes
end

platform :android do
  # Android specific lanes
end

Our Fastfile starts with a before_all hook to ensure our repository has the latest code from our master branch and is clean so we don't deploy unfinished code. Next, we define the platforms for which we are going to later provide lanes. By defining our platforms individually, we are able to execute builds and deployments for each platform individually.

iOS and the App Store

Let's start with iOS and our first lane which will fetch our App Store certificates and provisioning profiles. We are going to use match to easily sync our certificates and profiles, allowing us to share one code signing identity across our entire development team.

match creates all required certificates & provisioning profiles and stores them in a separate git repository. Every team member with access to the repo can use those credentials for code signing. match also automatically repairs broken and expired credentials. It's the easiest way to share signing credentials across teams

Before creating our lanes, we need to setup match in our project and provide it with the URL to our private Git repo which will hold our code signing identities. Remember, code signing identities SHOULD ALWAYS be kept private.

$ fastlane match init

This will create a Matchfile in our fastlane directory within our project root and will be used anytime we use a match action in our lanes. Now let's setup our first lane to fetch our certificates and provisioning files using our Matchfile settings. Each lane should be proceeded by a description using desc that clearly describes purpose of the lane. Next, we define the name of our lane and provide it with the actions it should execute.

platform :ios do
  desc 'Fetch certificates and provisioning profiles'
  lane :certificates do
   match(app_identifier: 'com.bundle.identifier', type: 'development', readonly: true)
   match(app_identifier: 'com.bundle.identifier', type: 'appstore', readonly: true)
  end
end

Now we can run fastlane ios certificates to automatically save our provisioning profiles and certificate to our macOS keychain for future use. We are now also able to use our certificates lane as an action in our other iOS-specific lanes. Our next step is to create a lane for building our iOS app and another one for shipping our app to Testflight.

platform :ios do
  desc 'Fetch certificates and provisioning profiles'
  lane :certificates do
   match(app_identifier: 'com.bundle.identifier', type: 'development', readonly: true)
   match(app_identifier: 'com.bundle.identifier', type: 'appstore', readonly: true)
  end

  desc 'Build the iOS application.'
  private_lane :build do
    certificates
    increment_build_number(xcodeproj: './ios/AppName.xcodeproj')
    gym(scheme: 'AppName', project: './ios/AppName.xcodeproj')
  end

  desc 'Ship to Testflight.'
  lane :beta do
    build
    pilot
    commit_version_bump(message: 'Bump build', xcodeproj: './ios/AppName.xcodeproj')
    push_to_git_remote
  end
end

Let's break our two new lanes down a bit and start with our build lane. We are using a private_lane block since it doesn't make sense to only execute a build task directly from the command line using fastlane build. Inside our build block will call our certificates lane and execute it before we automatically increment our app build number in Xcode and then build our app using gym.

Now, we have all the pieces in place to ship our app to Testflight using our beta lane. When we run fastlane ios beta, fastlane executes our build lane to build our signed app .ipa bundle. We then use pilot to upload our local build to Testflight. Finally, we also create a new commit in our repo for the build number bump and push that to our Git remote automatically.

Android and the Google Play Store

Similar to our iOS-specific lanes, we need to provide a few Android-specific lanes to build and deploy our app to the Google Play Store. Unlike iOS, we don't need to create a specific code signing lane since our Android app will be signed during an assemble task in our build action. This does however assume that we have already setup app signing for our app in the Google Play store. I followed the guide provide by Facebook in the React Native docs before proceeding.

platform :android do
  desc 'Build the Android application.'
  private_lane :build do
    gradle(task: 'clean', project_dir: 'android/')
    gradle(task: 'assemble', build_type: 'Release', project_dir: 'android/')
  end
end

Our build lane has two gradle actions, the first of which cleans our android directory before we move on actually building our app using assemble. With our build lane complete, we'll now create lanes for the release tracks on the Google Play Store.

platform :android do
  desc 'Build the Android application.'
  private_lane :build do
    gradle(task: 'clean', project_dir: 'android/')
    gradle(task: 'assemble', build_type: 'Release', project_dir: 'android/')
  end

  desc 'Ship to Play Store Beta.'
  lane :beta do
    build
    supply(track: 'beta', track_promote_to: 'beta')
    git_commit(path: ['./android/gradle.properties'], message: 'Bump versionCode')
    push_to_git_remote
  end

  desc 'Ship to Play Store Alpha.'
  lane :alpha do
    build
    supply(track: 'alpha', track_promote_to: 'alpha')
    git_commit(path: ['./android/gradle.properties'], message: 'Bump versionCode')
    push_to_git_remote
  end

  desc 'Ship to Play Store Internal.'
  lane :internal do
    build
    supply(track: 'internal', track_promote_to: 'internal')
    git_commit(path: ['./android/gradle.properties'], message: 'Bump versionCode', allow_nothing_to_commit: true)
    push_to_git_remote
  end
end

We now have three lanes, one for each release track. Using fastlane android OUR_LANE_NAME, each lane calls our build lane, uses supply to upload that build to the defined Google Play Store track, bumps our app versionCode and pushes that bump to our Git repo.

Depending on your release flow, you may only need to use the internal track lane and from there use the Google Play Console to promote the internal release up to the Alpha and Beta tracks before releasing it to the public. However, it can be nice to have the other lanes available if there is ever the need to ship directly to a specific track.

Fastlane to the Rescue

Fastlane has saved my sanity when dealing with code signing and app store deployments for my React Native projects. In a team environment, it's a no brainer to use fastlane actions like match to share certificates and provisioning profiles. Beyond building, code signing and deployment, fastlane has a slew of available actions that can automate your development flow process even more. Whether it's running tests, generating app screenshots, creating automatic documentation or just sending an automated Slack message to the rest of your team that a new build is available, fastlane can help automate all of those processes.


AndroidAppsDevelopmentReact NativeiOS