Swift/UnwrapDiscover about Swift, iOS and architecture patterns

Environment variables with Dotenv in Fastlane

August 25, 2020

You probably know and use Fastlane. Over the years it became de-facto continuous delivery tool for iOS platform. But do you know about one of its most secret and yet powerful feature: dotenv?

This is an adaptation of a former version I wrote some years ago in French 🇫🇷

dotenv

dotenv is a ruby gem allowing to handle multiple environments using dot files. And guess what? Fastlane come with built-in support through --env parameter:

fastlane distribute --env prod

Using --env option Fastlane/dotenv will load following files, adding defined values into ENV environment:

  • .env
  • .env.default
  • .env.<env-option> (in our example, .env.prod)

If a value is defined into multiple files then the last one win. That is very useful to define default values for example (more on that later).

Fastlane

Let's use a basic Fastfile with no dotenv. To handle environments we have 2 options:

  1. define a hash with values per environment
  2. define one line per environment (distribute**)
my_environment[:prod] = {
  :SLACK_URL => '...',
  :CRASHLYTICS_KEY => '...',
  :SCHEME => '...',
  ...
}

my_environment[:dev] = {
  :SLACK_URL => '...',
  :CRASHLYTICS_KEY => '...',
  :SCHEME => '...',
  ...
}

before_all do |options|
  env = // determine environment one way or another
end

lane :distribute do
  gym(my_environment[env])
  appstore() if env == "prod"
  firebase() if env == "dev"
end
lane distribute_prod do |options|
  distribute(...)
  appstore()
end

lane distribute_dev do |options|
  distribute(...)
  firebase()
end

private_lane distribute do |options|
  distribute(...)
end

Both versions are pretty verbose and hide a little bit what we're trying to actually do (declare a distribute lane). Let's try to migrate this Fastfile to use dotenv and compare the result.

dotenv + Fastlane

We can first move common values into .env.default file. This file is the perfect place for values common across all environments or default values which may change from time to time in some environments.

// .env.default
SLACK_URL = "..."

CRASHLYTICS_KEY = "..."

You can also use .env file instead of .env.default or both of them.

We can then move environment variables into their dedicated files:

// .env.preprod
GYM_SCHEME = "Scheme-Preprod"
GYM_EXPORT_METHOD = appstore
UPLOAD_PLATFORM = "testflight"

// .env.dev
GYM_SCHEME = "Scheme-Dev"
GYM_EXPORT_METHOD = development
UPLOAD_PLATFORM = "firebase"

Last but not least our Fastfile now look as above:

lane :distribute do
  gym()
  testflight() if ENV['UPLOAD_PLATFORM'] == 'testflight'
  firebase() if ENV['UPLOAD_PLATFORM'] == 'firebase'
end

Pretty simple! 👌

You may have noticed we didn't pass any parameter to gym: Fastlane lanes have actually native support for configuration through ENV. Gym will automatically use GYM_SCHEME and GYM_EXPORT_METHOD values! Which make our Fastfile even lighter! 💪

To know which environment variables you can use with a lane take a look at its documentation.For instance fastlane action gym will show supported environment variables for Gym.

Let's recap

Using dotenv we gained:

  1. 🤏A smaller Fastfile: less lanes, no environment code/hash
  2. 💯Extensibility: adding a new environment is just adding a dot file. No impact on our Fastfile
  3. 📖Flexible configuration: as already stated, lanes have support for configuration through environment. This open wide possibilities
  4. 🚀A generic Fastfile! Want to reuse it across multiple projects? Go ahead! That's actually what I've been doing for quite some time with my own one

We however may have lost a little bit explicitness because of the auto-magically use of the ENV variables. I think the tradeoff is worth it though as they can still be used explicitly if needed/preferred or some sort of guard can be added at the very beginning of lanes.