Article

Streamlining Your Flutter Deployment Process with Firebase App Distribution Using Fastlane: Part 1

October 15, 2024

 

 

 

 

 

 

By James Preston | Senior Software Engineer & Engineering Manager

For mobile developers working with Flutter, deploying apps to multiple platforms can quickly become a repetitive and time-consuming task. By leveraging tools like Fastlane and Firebase App Distribution, you can automate these processes, allowing you to focus on writing high-quality code and other important aspects of development.

In this guide, we’ll cover how to set up Fastlane with Flutter to streamline deployment, manage app signing, and integrate with CI/CD pipelines. Specifically, we’ll focus on adding Fastlane to the iOS portion of your Flutter project and automating its deployment to Firebase App Distribution using GitHub Actions.

Step 1: Installing Fastlane with Homebrew

To get started with Fastlane, install it via Homebrew by running:

brew install fastlane

Once installed, verify that Fastlane is set up correctly with:

fastlane --version

Now you’re ready to configure Fastlane for your Flutter project!

Step 2: Configuring Fastlane for Flutter (iOS)

Initialize Fastlane for iOS

Navigate to your Flutter project’s ios directory:

cd ios
fastlane init

Fastlane will guide you through the setup process by searching for iOS and Android projects in the directory and creating the necessary configuration files, including the Fastfile and Appfile.

Select Manual Automation

When prompted to choose what  you would like to use fastlane for, select Manual Setup for this example. Once the setup is complete, Fastlane will provide a list of available actions, along with links to further documentation, such as generating screenshots, beta distribution, App Store deployment, and code signing.

Example Fastfile for iOS

Here’s a basic example of a Fastfile for building and distributing your iOS app:

default_platform(:ios)
platform :ios do
  # Define a lane to build the iOS app
  desc "Build the iOS app"
  lane :build do
    build_ios_app(
      scheme: "Runner", # By default, Flutter projects' scheme is called Runner
      export_options: {
        provisioningProfiles: {
          "com.dxsolo.fastlaneHelloWorld" => "Fastlane Prov" # Update with your app bundle ID and provisioning profile name
        },
        method: "ad-hoc" # Use the method that matches your provisioning profile (e.g., 'ad-hoc', 'app-store', 'development')
      }
    )
  end

  # Define a lane to build and distribute the iOS app via Firebase App Distribution
  desc "Build and distribute iOS app"
  lane :distribute_ios do
    build_ios_app(
      scheme: "Runner",
      export_options: {
        provisioningProfiles: {
          "com.dxsolo.fastlaneHelloWorld" => "Fastlane Prov" # Ensure provisioning profile is properly set
        },
        method: "ad-hoc", # Matches the method used for your provisioning profile
      }
    )
    firebase_app_distribution(
      app: "", # Add your Firebase app ID here
      groups: "", # Add your Firebase testers group here
      release_notes: "", # Optionally add release notes for testers
    )
  end
end

Here is an example of the AppFile

Automating iOS App Builds

In most cases, Fastlane will work seamlessly with Xcode 9 and above. For manual code signing, ensure you have the necessary certificates and provisioning profiles downloaded on your machine.

Manual signing iOS apps 

To build and distribute your Flutter app for iOS, you can define a simple lane in the fastfile:

desc "Build the iOS app"
lane :build do
  build_ios_app(
    scheme: "Runner" # by default Flutter projects scheme is called Runner,
    export_options: {
      provisioningProfiles: {
        "com.dxsolo.fastlaneHelloWorld" => "Fastlane Prov"
      },
      method: "ad-hoc",
    }
  )
end

To build the iOS app, simply run:

fastlane build

After configuring Firebase App Distribution (covered later), you’ll be able to deploy the app with:

fastlane distribute_ios

Signing iOS Apps Automatically with Match

Fastlane’s match tool simplifies iOS code signing. Here’s how to set it up:

  1. Create an empty private GitHub repository to store provisioning profiles.

Initialize match locally by running:

fastlane match init

     2. Choose git as the storage option when prompted and paste your repository’s URL.

     3. Use fastlane match adhoc to create the required profiles for ad-hoc distribution. 

     4. Create a passphrase you can remember , you will need this later for the ENV MATCH_PASSWORD in the CI/CD pipeline.

     5. Enter a Apple developer account username (use a company wide one)

     6. You will likely get an error that “An app with that bundle ID needs to exist in order to create a provisioning profile for it”. In this case simply run the following command which will be in the console output enabling you to copy/paste

fastlane produce -u -a com.dxsolo.fastlaneHelloWorld --skip_itc

     7.  Now back over to your project run the command

fastlane match adhoc

     8. Modify your Fastfile to use match for managing code signing:

default_platform(:ios)
platform :ios do
  desc "Build the iOS app"
  lane :build do
  	match(type: "adhoc", app_identifier: "com.dxsolo.fastlaneHelloWorld" ,   readonly: is_ci, git_url: [gitURL])
    build_ios_app(
      scheme: "Runner", # by default Flutter projects scheme is called Runner,
    )
  end

  desc "Build and distribute iOS app"
  lane :distribute_ios do
    build
    firebase_app_distribution(
      app: "[[APP_ID]]",
      groups: "dev",
      release_notes: "Hello World from Fastlane",
    )
  end
end

    9. The build lane simply calls the build_ios_app action now without the need to specify the export options. To test this out run the command

fastlane build

Step 3: Integrating Firebase App Distribution

Firebase App Distribution allows you to easily share builds with testers. To integrate it with Fastlane, follow these steps:

  1. Go to the Firebase Console and create a new project.
  2. Add an iOS app to your project and grab the App ID from the settings.

Install the Firebase CLI:

npm install -g firebase-tools

     3. Authenticate by running:

firebase login:ci

     4. There are a few ways to do this but for purposes of local deployments you can put this token into an Environment variable called $FIREBASE_TOKEN which the deployment will look for,

     5. Set up the firebase_app_distribution plugin by running:

fastlane add_plugin firebase_app_distribution

In your Fastfile, move match to the distribute_ios lane and remove the manually set export options:

default_platform(:ios)
platform :ios do
  desc "Build the iOS app with Match"
  lane :build do
    build_ios_app(
      scheme: "Runner", # by default Flutter projects scheme is called Runner,
    )
  end

  desc "Build and distribute iOS app"
  lane :distribute_ios do
    match(type: "adhoc", app_identifier: "com.dxsolo.fastlaneHelloWorld" , readonly: is_ci, git_url: ENV['MATCH_GIT_URL'])
    build
    firebase_app_distribution(
      app: "[[APP ID]]",
      groups: "dev",
      release_notes: "Hello World from Fastlane",
    )
  end
end

Now, you can deploy your app directly from the terminal with:

fastlane distribute_ios

Once this step finishes successfully you should be able to take a look and see the release in the firebase console and have been notified via email.

Auto Increment Build Number

To accomplish this you simply need to update the fastfile once again and use the provided fastlane actions

default_platform(:ios)

platform :ios do
  desc "Build the iOS app with Match"
  lane :build do
    build_ios_app(
      scheme: "Runner", # by default Flutter projects scheme is called Runner,
    )
  end

  desc "Increment the build number"
  lane :increment_version do
    latest_release = firebase_app_distribution_get_latest_release(
      app: "[[APP ID]]"
    )
    increment_build_number({ build_number: latest_release[:buildVersion].to_i + 1 })
  end


  desc "Build and distribute iOS app"
  lane :distribute_ios do
    increment_version
    match(type: "adhoc", app_identifier: "com.dxsolo.fastlaneHelloWorld" , readonly: is_ci, git_url: ENV['MATCH_GIT_URL'])
    build
    firebase_app_distribution(
      app: "[[APP ID]]",
      groups: "dev",
      release_notes: "Hello World from Fastlane",
    )
  end
end

Now that this is in place let’s run the distribute_ios lane once again and now when we look at the console we will see 2 versions

Step 4: Automating with CI/CD

By automating your deployment pipeline, you can trigger Fastlane to build and distribute your Flutter app automatically whenever new code is pushed to your repository. Popular CI/CD platforms like GitHub Actions, CircleCI, or Bitrise can integrate with Fastlane easily.

GitHub Actions Example

Let’s create a basic example of a GitHub Actions workflow that uses Fastlane to build and distribute a Flutter app to Firebase App Distribution:

In order to use the following workflow you need to set up a few things.

  • Create a PAT token which will be used to access the match repo
  • Create Secrets
  • Add setup_ci() to the fast file which will create a temporary keychain on the runner
default_platform(:ios)
setup_ci()
platform :ios do
  desc "Build the iOS app with Match"
  lane :build do
    build_ios_app(
      scheme: "Runner", # by default Flutter projects scheme is called Runner,
    )
  end

  desc "Increment the build number"
  lane :increment_version do
    latest_release = firebase_app_distribution_get_latest_release(
      app: "[[APP ID]]"
    )
    increment_build_number({ build_number: latest_release[:buildVersion].to_i + 1 })
  end


  desc "Build and distribute iOS app"
  lane :distribute_ios do
    increment_version
    match(type: "adhoc", app_identifier: "com.dxsolo.fastlaneHelloWorld" , readonly: is_ci, git_url: ENV['MATCH_GIT_URL'])
    build
    firebase_app_distribution(
      app: "[[APP ID]]",
      groups: "dev",
      release_notes: "Hello World from Fastlane",
    )
  end
end

Create a Personal Access Token

We need to give github actions permission to access the match repo. There are a myriad of ways to do this but for this example we will use a PAT.

  • In Github go to your profile settings and click on <> Developer Settings

  • Go to Personal access tokens and choose Tokens(classic)
  • Set the note and expiration to whatever you want
  • Give this PAT the repo scope
  • Copy the token and enter it as a secret in the repo called REPO_ACCESS_TOKEN

You repository secrets should look like 

  • MATCH_PASSWORD
    • This is the password you used for keychain_password when creating the match repo
  • FIREBASE_TOKEN
    • This is the token you create by running the command firebase login:ci on your local machine. Ideally you probably want to move over to a service account based authentication.
  • REPO_ACCESS_TOKEN
    • The Personal Access Token you created

iOS Example

name: Build and Distribute iOS
on:
  pull_request:
    branches:
      - master
  workflow_dispatch:

jobs:
  build:
    runs-on: macos-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
    
      - name: Set up Dart SDK
        uses: dart-lang/setup-dart@v1
        with:
          sdk: '3.4.3'
      
      - name: Set up Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.22.2'

      - name: Install dependencies
        run: flutter pub get

      - name: Set up Fastlane
        run: |
          cd ios
          bundle install
      
      - name: Build iOS and distribute with Fastlane
        env:
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
          MATCH_GIT_URL: "https://${{ secrets.REPO_ACCESS_TOKEN }}@github.com/JamesKPreston/fastlane_match.git"
        run: |
          cd ios
          fastlane distribute_ios

In this workflow:

  • It runs on macos-latest as iOS builds require macOS.
  • After checking out the code, it installs Flutter and the necessary dependencies.
  • Fastlane is set up with bundle install, which ensures the correct version of Fastlane is installed.
  • Finally, Fastlane builds and distributes the iOS app using the distribute_ios lane defined in the iOS Fastfile.

With this workflow, your Flutter app will automatically be built and distributed to testers on Firebase App Distribution whenever changes are pushed to the main branch.

Step 5: Best Practices

Best Practices:

  • Secure credentials: Use environment variables or Fastlane’s dotenv integration to manage sensitive data.
  • Local testing: Always test your Fastlane lanes locally before integrating them with CI/CD.
  • Keep Fastlane updated: Regular updates ensure compatibility with new Flutter, iOS, or Android versions.
  • Version control: Track changes to your Fastfile for easier troubleshooting.

Resources

Conclusion: Simplifying Flutter App Deployment

By incorporating Fastlane and Firebase App Distribution, you can automate the entire build and deployment process for your Flutter apps, saving valuable development time. With this setup, you’ll be able to focus on what matters most—writing great code and delivering features quickly. Why not streamline your next project with Fastlane and Firebase?