How I increased my productivity during deployments using fastlane

Jacob Muchow
QuarkWorks, Inc.
Published in
8 min readSep 3, 2017

I am a senior Android developer at Quarkworks where I work as the team lead for hello Network’s Android app. My job as team lead on the Hello Android project includes interfacing with Hello’s other teams (design, marketing, translations, backend, iOS), team management, code review and deployment. I also contribute quite a bit of code to the project which can include anything from software architecture to bug fixes. I don’t keep specific enough track of my hours to say this for sure (I tend to juggle a lot of things at once), but I would guess I spend about 60% of my time doing “management” tasks and 40% coding. Given the debt I incur from context switching, I can go several days feeling like I didn’t get any significant coding done.

As one of the stronger programmers on the team, I would like to maximize the amount of time and focus I can put into writing code. Given the overhead I’m taking on as team lead, I’m often thinking of ways to improve my productivity on the management side of things so I can spend more time on the tangibles: adding features, fixing bugs, and helping out with any other tasks we need to get done.

On Hello Android, we have a unique setup on the Play Store: there is one Play Store account & entry for our Indian users, which is in beta, and an entirely different one for everyone else (U.S., Brazil, Portugal…), which has been in production for a little over a year. When we make a new release to the main app, we do staged rollouts starting at 25%, then bumping to 50%, 75% and 100% of our user base over 6 hour intervals.

During the initial 25% rollout for the main app, we go ahead and do a full rollout for the India app since the Beta channel on Google Play doesn’t support staged rollouts.

Workflow

Code that is production-ready is merged into the master branch in our Git repository. When I make a production build, I increment the version number and name in Gradle, make a commit, then generate a signed APK for upload to Google Play. Once I upload and start the 25% rollout, I checkout the india branch, merge in master and make a release on Google Play Beta with the same version code and name.

Sounds like a lot of work, right? This process can eat pretty big chunks of my time. Luckily that’s where fastlane comes in. fastlane is a build automation

toolset for Android and iOS projects which helps you chain the steps in your deployment process together into a “lane”, which can be executed with a single command line action. For example, you could create a custom lane named “release” which:

  1. Generates a signed APK.
  2. Uploads & deploys the APK to Google Play Beta channel.
  3. Posts a message to your team’s Slack channel when done.

You execute this entire flow in the command line using fastlane release.

This is just a simple example — there are plenty of features and plugins ready to be used so you can easily customize your lane to do your whole deployment workflow.

The essential fastlane setup consists of two files: Fastfile and Appfile. The Fastfile is where you define your custom actions called “lanes”. These are composed of one or more build steps like in the example below:

Fastfile

lane :release do
gradle(task: 'clean')
# build the release variant
gradle(task: 'assembleRelease')
# upload production APK to Google Play
supply(track: 'beta')
end

Some of these build steps may take the same parameters, so the Appfile can be used to store defaults for these commands such as your package name or credentials:

Appfile

package_name "com.hello.application"
json_key_file "com.hello.application_key.json"

In order to publish to Google Play Google Play using fastlane’s supply tool, there is some initialization that must occur — you have to have your app’s metadata from the store saved locally. I expected this to be pretty tricky given our setup with two play store entries, but it actually wasn’t that bad. supply is extensive enough to handle multiple package names (app IDs) and separate metadata. Instead of doing the basic fastlane supply init, I ran it twice with extra parameters for each of our app store entries:

fastlane supply init -p com.hello.application -m fastlane/prod -j com.hello.application_key.jsonfastlane supply init -p com.hello.network -m fastlane/india -j com.hello.network_key.json

Normally, I would keep “India” code changes and files on the india branch in git, but I decided to put the metadata in .gitignore instead since it takes up a lot of memory and doesn’t need to be in the repository. Separating them into different folders let me keep both on my computer so I can toggle back and forth easily. I’m not actually using supply’s metadata capabilities right now, but it is necessary to have it synced for using the tool.

To create a signed APK, fastlane relies on Gradle commands. By default, if you try to generate a release APK from the command line using the assembleRelease command, an APK will be generated, but it will not be signed with your release key, and will be rejected by Google Play. In order to generate signed APKs from the command line, it is necessary to provide the keys and passwords to your signing keystore during the Gradle build:

build.gradle

signingConfigs {
release {
storeFile "key_store.jks"
storePassword "your store pass"
keyAlias "your key alias"
keyPassword "your key pass"
}
}

buildTypes {
...
}

I didn’t feel comfortable with the idea of embedding our signing passwords into the project, so I decided on the following solution:

keystore.properties (ignored in .gitignore)

RELEASE_STORE_FILE=key_store.jks
RELEASE_STORE_PASSWORD=my store pass
RELEASE_STORE_KEY_ALIAS=my key alias
RELEASE_STORE_KEY_PASSWORD=my key pass

build.gradle

def isAutoSigning = new File("keystore.properties").exists()
if (isAutoSigning) {
Properties props = new Properties()
props.load(new FileInputStream(file("../keystore.properties")))

signingConfigs {
release {
storeFile file(props['RELEASE_STORE_FILE'])
storePassword props['RELEASE_STORE_PASSWORD']
keyAlias props['RELEASE_STORE_KEY_ALIAS']
keyPassword props['RELEASE_STORE_KEY_PASSWORD']
}
}
}
buildTypes {
...
}

This method allows me to keep the passwords in a separate file named keystore.properties which I include in our .gitignore, thereby protecting our passwords by keeping them on my computer alone. In Gradle, we check if the keystore.properties file exists before trying to set the signing configurations. While I am the only one that makes release builds, I found that the Gradle build would still run this code for debug builds and would crash when the keystore.properties file could not be found.

Note the change from “keystore.properties” to “../keystore.properties”. The android block in your build.gradle file changes the file scope, so that’s why this is done. For reference, my “keystore.properties” file is at the top level of the project, while the the Gradle file is in app/build.gradle.

Having done all this, I was able to successfully sign and upload a new beta build to Google Play by simply using the command fastlane release. In my case, I needed to add the parameter metadata_path: 'fastlane/prod' to the supply command in my release lane, but if you do the default behavior, this is not necessary. Beyond this, I also specify that I’m not going to upload new metadata, images or screenshots using extra paramters. You can check what kinds of parameters are available for use and their descriptions using fastlane supply --help .

Fastfile

lane :release do
gradle(task: 'clean')

# build the release variant
gradle(task: 'assembleRelease')

# upload production APK to Google Play
supply(
track: 'beta',
metadata_path: 'fastlane/prod',
skip_upload_apk: false,
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true,
check_superseded_tracks: true
)
end

One thing they don’t mention in the documentation is that the check_superseded_tracks parameter will be pretty important if you are doing more than just pushing to production. The Google Play Developer API will reject any uploaded APK’s that “supersede” those on other tracks. For example, if you have something like build 202 in Alpha, and are uploading build 203 to Beta, then the API will give you an error. The check_superseded_tracks parameter will deactivate any APKs which would be superseded so you can push your new one. In the example, this means the 202 build would be deactivated in Alpha, then the 203 build will be uploaded to Beta.

There are some other finicky situations that can arise with the Google Developer API for track uploads as well. For example, if you have unpublished release drafts in a track, then you’ll get a confusing error. This can be fixed by discarding those edits in the web console.

Here is a look at our final Fastfile if you are interested:

platform :android do

before_all do
ENV["SLACK_URL"] = "https://hooks.slack.com/services/<secret ids>"
end

desc "Submit a new release build to google play"
lane :release do
gradle(task: 'clean')

# build the release variant
gradle(task: 'assembleRelease')

# upload production APK to Google Play
supply(
track: 'rollout',
rollout: '0.25',
metadata_path: 'fastlane/prod',
skip_upload_apk: false,
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true,
check_superseded_tracks: true
)

slack({
message: "Android uploaded to production at 25%."
})
end
end

In and of itself, this doesn’t save me too much time, but by combining some other things with fastlane, I’ve been able to make some pretty powerful changes:

  1. Configure release builds to generate multiple APKs split by processor type. This decreased our app size by 27.1%. Read how to do this here. Uploading several different APKs to the store for one build wasn’t really practical until I started doing it with fastlane. Now it takes 4–5 minutes to generate and deploy 5 APKs for one release. If you want to do this, supply should figure it out automatically if you make the changes in Gradle.
  2. Run deployments on a remote server such as Jenkins. This lets me press a button in a Jenkins GUI which spins up the deployment process on a short-lived process. This leaves me free to change branches on my computer and work on other things.

As ever, QuarkWorks is available to help with any software application project — web, mobile, and more! If you are interested in our services you can check out our website. We would love to answer any questions you have! Just reach out to us on our Twitter, Facebook, LinkedIn, or Instagram.

--

--

Jacob Muchow
QuarkWorks, Inc.

Just trying to do something great. Software engineer and co-founder at QuarkWorks, Inc. Working on realtime animations for digital characters using your camera.