27.01.2025 - Dario Digregorio - 7 min read Part 2: Releases Flutter CI/CD with Fastlane and GitHub Actions

Introduction

In the previous article, we saw how to set up Fastlane and GitHub Actions for the sample app. In this article, we will see how to further automate the release process further with versioning, GitHub Releases and promotion using Fastlane and GitHub Actions.

Repository: Flutter App CI/CD using Fastlane and GitHub Actions

Prerequisites

  • Follow the first part of the guide to set up Fastlane and GitHub Actions.

Creating the Tag workflow

We will create a new workflow that will bumps the pubspec.yaml version, creates a new tag, and pushes the changes to the repository.

  1. Create a new file in the .github/workflows directory, e.g. tag.yml.
  2. Add the following content to the tag.yml file:
    name: Tag Release
    on:
        workflow_dispatch:
            inputs:
              action:
                type: choice
                description: Action type
                default: none
                options:
                - major
                - minor
                - patch
                - none
    jobs:
      tag:
        concurrency:
          group: ${{ github.workflow }}-${{ github.ref }}
          cancel-in-progress: true
        name: Tag
        runs-on: self-hosted
        permissions:
            # Give the default GITHUB_TOKEN write permission to commit and push the
            # added or changed files to the repository.
            contents: write
    
        steps:
          - uses: actions/checkout@v4
            with:
                ref: ${{ github.head_ref }}
                fetch-depth: 0 # Fetch the complete history for tags and branches
          - uses: stikkyapp/update-pubspec-version@v2
            id: update-pubspec-version
            with:
                strategy: ${{ github.event.inputs.action }}
                bump-build: true
          - uses: stefanzweifel/git-auto-commit-action@v5
            with:
              commit_message: "Bump version to ${{ steps.update-pubspec-version.outputs.new-version }}"
              commit_user_name: GitHub Actions
              tagging_message: release/v${{ steps.update-pubspec-version.outputs.new-version }}
      
    This workflow can be triggered manually and accepts an input on what action you want to perform. There are four options: major, minor, patch and none. Each action will bump the version accordingly. See this for more information on semantic versioning. We use the update-pubspec-version action to bump the version in the pubspec.yaml file and the git-auto-commit-action to commit the changes and create a new tag with the format release/v1.0.0. Each run of the workflow will always bump the build number.
  3. Trigger the workflow manually by going to the Actions tab in your GitHub repository and selecting the Tag Release workflow. Click on the Run workflow button and select the branch you want to tag.
  4. Sit back and watch the magic happen! ✨ You should see a new commit and tag in your repository.
Tag Deploy

Creating the Release workflow

  1. Create a new file in the .github/workflows directory, e.g. release.yml.
  2. Add the following content to the release.yml file:
    name: Create Release
    on: 
      push:
        tags:
        - 'release/*'
    
    jobs:
      release:
        concurrency:
          group: ${{ github.workflow }}-${{ github.ref }}-release
          cancel-in-progress: true
        runs-on: self-hosted
    
        steps:
        - uses: actions/checkout@v4
        - name: Create Release
          uses: ncipollo/release-action@v1
          with:
            name: ${{ github.ref_name }}
            tag: ${{ github.ref }}
            generateReleaseNotes: true
    This workflow is triggered whenever a new tag with the format release/* is pushed to the repository. The action will create a new release with the tag name and generate the release notes based on the commit messages and merged branches since the last tag.
  3. We are not done yet. Triggering the Tag workflow doesn’t trigger the release workflow yet. This is due to a limitation of GitHub. Follow this guide to create a token with the scope repo. Add the token to your repository as a secret with the name PAT. Now we need to add the token to the checkout action in the tag.yml file:
    # Remove from here 
    permissions:
      # Give the default GITHUB_TOKEN write permission to commit and push the
      # added or changed files to the repository.
      contents: write
       # Until here
    
    steps:
      - uses: actions/checkout@v4
        with:
            ref: ${{ github.head_ref }}
            fetch-depth: 0 # Fetch the complete history for tags and branches
            token: ${{ secrets.PAT }}
    Since the checkout action uses the GITHUB_TOKEN by default, we need to add the PAT token to the action to trigger the Release workflow. We can then remove the permissions part. It is better to trigger the workflow manually only, otherwise the workflow will run in a loop.
  4. Now trigger the Tag workflow again and you should see a new release in your repository after the Release workflow` has finished successfully. Release
  5. To trigger the Build and Deploy workflow whenever a new tag is created. Add this to your deploy.yml file:
    on:
      workflow_dispatch:
      push:
        tags:
          - 'release/*'
    This will trigger the Build and Deploy workflow whenever the Tag workflow creates a new tag. This way you can automate the whole process from versioning to deployment.

Creating the Promote workflow

The idea is to have a workflow that promotes a release to a specific track in the PlayStore. This can be useful if you want to promote a release from the internal track to the production track, or from TestFlight to the AppStore for example.

  1. Add this lane to android/fastlane/Fastfile:

    platform :android do
        # ...
        desc "Promote version"
        lane :promote do |options|
          skip = options[:skip] || true
          version = flutter_version()
          upload_to_play_store(
            track: "internal",
            track_promote_to: "production",
            skip_upload_metadata: false,
            skip_upload_images: skip,
            skip_upload_screenshots: skip,
            track_promote_release_status: "draft",
            version_code: version["version_code"],
            version_name: version["version_name"],
          )
      end
    end
    Add this lane to ios/fastlane/Fastfile:
    platform :ios do
      # ...
      desc "Promote version"
      lane :promote do |options|
        skip = options[:skip] || true
        version = flutter_version()
        deliver(
          submit_for_review: false,
          automatic_release: true,
          force: true,
          skip_screenshots: skip,
          skip_binary_upload: true,
          overwrite_screenshots: true,
          app_version: version["version_name"],
          precheck_include_in_app_purchases: false
        )
      end
    end
    We added a promote lane for both Android and iOS. The lane will promote the release from the internal track or TestFlight to the production track in the PlayStore and AppStore respectively. We also added an option to skip the screenshots upload.

  2. Before you can promote a release, you must have uploaded and published at least one release manually already. Make sure you have a release on the production track in the PlayStore and the app published in the AppStore.

  3. Run fastlane promote skip:true to start the lane and promote your version to the respective track. Make sure to run the command in the respective platform folder.

  4. After successfully promoting the release, you can now automate the process using GitHub Actions.

  5. Create a new file in the .github/workflows directory, e.g. promote.yml.

  6. Paste this content into the promote.yml file:

    name: Promote Release
    on:
      workflow_dispatch:
        inputs:
          skip:
            type: boolean
            description: skip screenshots
            default: true
    
    jobs:
      android:
        concurrency:
          group: ${{ github.workflow }}-${{ github.ref }}-android
          cancel-in-progress: true
        name: Promote Android
        runs-on: self-hosted
    
        steps:
          - uses: actions/checkout@v4
          - name: Decode sec json file
            run: echo "${{ secrets.SEC_JSON }}" | openssl base64 -d -out ./android/sec.json
          - name: Promote Release on Play Store
            uses: maierj/fastlane-action@v2.3.0
            with:
              subdirectory: android
              lane: promote
    
      ios:
        concurrency:
          group: ${{ github.workflow }}-${{ github.ref }}-ios
          cancel-in-progress: true
        name: Promote iOS
        runs-on: self-hosted
        env:
            FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
        steps:
          - uses: actions/checkout@v4
          - name: Promote Release on App Store
            uses: maierj/fastlane-action@v2.3.0
            with:
              subdirectory: ios
              lane: promote skip:${{ github.event.inputs.skip }}
    This workflow can be triggered manually and accepts an input if you want to upload screenshots as well. The workflow will take a release and promote it to the respective track in the PlayStore or AppStore.

  7. Trigger the workflow manually by going to the Actions tab in your GitHub repository and selecting the Promote Release workflow. Click on the Run workflow button and select the branch or tag you want to promote. Make sure that the version you want to promote has already been uploaded and published to the respective track. You can do this with the Deploy workflow.

Recap

Overview

So how does the whole process look like now? Whenever you want to release a new version of your app, you can manually trigger the Tag Release workflow. You can choose between major, minor and patch. The workflow will bump the version in the pubspec.yaml file, create a new commit and tag, and push the changes to the repository. This will trigger the Release workflow which will create a new release with the tag name and generated release notes. This will also trigger the Build and Deploy workflow which will build and deploy the app to the AppStore and PlayStore. After you have successfully deployed the app, you can manually trigger the Promote Release workflow. This will promote the release to the respective track in the PlayStore and AppStore.

Conclusion

And that’s it! You’ve now set up a CI/CD pipeline for your Flutter app using Fastlane and GitHub Actions. This setup will automatically build and deploy your app to the AppStore and PlayStore. You’ve also automated the versioning, tagging and release process with GitHub Actions. Time to kick back, relax, and let automation take care of the repetitive tasks.

Credits

Title image by Igor Dashko on iStock

Dario Digregorio

Senior Flutter Developer