← Articles

Automating Flutter builds with Fastlane

By John · 18 October 2024

Fastlane automates the most tedious parts of mobile deployment: building, signing, testing, taking screenshots, and submitting to stores. Here is how to set it up for a Flutter project and automate common workflows.

Setup

Fastlane is a Ruby tool:

# Install
gem install fastlane
# or via Homebrew:
brew install fastlane

# Initialize in your Flutter project root
fastlane init
# Choose "Manual setup"

This creates:

fastlane/
├── Appfile      # App identifiers
├── Fastfile     # Your lanes (automation scripts)
└── Matchfile    # Certificate management config (if using Match)

Appfile

# fastlane/Appfile
app_identifier "com.example.myapp"
apple_id "[email protected]"
team_id "XXXXXXXXXX"  # Apple Team ID

package_name "com.example.myapp"  # Android package name
json_key_file "fastlane/google-play-key.json"  # Google Play service account

Basic lanes

# fastlane/Fastfile
default_platform(:ios)

platform :ios do
  desc 'Run tests'
  lane :test do
    sh 'flutter test'
  end

  desc 'Build and upload to TestFlight'
  lane :beta do
    increment_build_number(
      xcodeproj: 'ios/Runner.xcodeproj',
      build_number: latest_testflight_build_number + 1,
    )

    build_ios_app(
      scheme: 'Runner',
      workspace: 'ios/Runner.xcworkspace',
      configuration: 'Release',
      export_method: 'app-store',
      output_directory: './build',
    )

    upload_to_testflight(
      api_key_path: 'fastlane/app_store_connect_api_key.json',
      skip_waiting_for_build_processing: true,
    )
  end

  desc 'Submit to App Store'
  lane :release do
    deliver(
      api_key_path: 'fastlane/app_store_connect_api_key.json',
      submit_for_review: true,
      automatic_release: false,
      force: true, # Skip HTML report
    )
  end
end

platform :android do
  desc 'Build and upload to Play Store internal track'
  lane :internal do
    sh 'flutter build appbundle --release'
    supply(
      track: 'internal',
      aab: '../build/app/outputs/bundle/release/app-release.aab',
    )
  end

  desc 'Promote from internal to production with 10% rollout'
  lane :promote_to_production do
    supply(
      track: 'internal',
      track_promote_to: 'production',
      rollout: '0.1',
    )
  end
end

Running lanes

bundle exec fastlane ios beta
bundle exec fastlane android internal
bundle exec fastlane ios test

Match: certificate management

Match stores certificates and provisioning profiles in a private Git repo. Every team member and CI machine gets the same certs automatically.

# First time: create the private certs repo, then:
bundle exec fastlane match init
# Enter the Git URL of your private certs repo

# Generate distribution certs
bundle exec fastlane match appstore

# In CI (read-only mode)
bundle exec fastlane match appstore --readonly
# fastlane/Matchfile
git_url "[email protected]:yourorg/mobile-certs.git"
type "appstore"
app_identifier "com.example.myapp"
username "[email protected]"

Automating screenshots

lane :screenshots do
  capture_ios_screenshots  # Uses UI tests to take screenshots
  frame_screenshots  # Add device frames
  upload_to_app_store(skip_binary_upload: true, skip_metadata: true)
end

GitHub Actions integration

name: Deploy to TestFlight

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.22.0'
          cache: true

      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
          bundler-cache: true

      - name: Setup Fastlane Match
        run: bundle exec fastlane match appstore --readonly
        env:
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }}

      - name: Deploy to TestFlight
        run: bundle exec fastlane ios beta
        env:
          APP_STORE_CONNECT_API_KEY_PATH: fastlane/api_key.json

      - name: Create API key file
        run: echo '${{ secrets.APP_STORE_CONNECT_API_KEY }}' > fastlane/api_key.json

Gemfile for reproducible builds

# Gemfile
source 'https://rubygems.org'

gem 'fastlane'
gem 'cocoapods'
bundle install
bundle exec fastlane  # Always run via bundle exec

Common pitfalls

Not using bundle exec. Running fastlane directly uses the system Ruby and Fastlane, which may differ from what your project expects. Always use bundle exec fastlane for reproducible behaviour.

Match with write access in CI. If two CI jobs run simultaneously, one can corrupt the other's certificate write. Always use --readonly in CI.

Hardcoding build numbers. If you call increment_build_number with a hardcoded value, two builds will conflict. Compute from latest_testflight_build_number + 1 or from the CI run number.

Sign in to like, dislike, or report.

Automating Flutter builds with Fastlane — ANN Tech