Skip to content

dario-digregorio/flutter_github_actions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

58 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Flutter CI/CD with Fastlane and GitHub Actions: The Basics (Part 1)

Flutter CI/CD

Introduction

If you're looking to streamline your Flutter app's deployment to the AppStore and PlayStore, you've come to the right place. We'll be using Fastlane and GitHub Actions to automate our workflow. By the end of this tutorial, you'll have an automated pipeline that builds and deploys your Flutter app to your preferred app stores. In this guide we will skip all the setup and configuration of Fastlane and GitHub Actions, and will focus on the Flutter and workflow specific parts of the setup.

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

Prerequisites

  • A Flutter app
  • Flavors and environment Variables set up in your Flutter app. You can follow the official Flutter guide to set this up.
  • You should be able to build a release version for Android and iOS. You can follow the Android guide and iOS guide to set this up.
  • A GitHub account with your app repo.
  • Fastlane installed on your local machine. You can follow the official Fastlane installation guide.
  • AppStore and PlayStore accounts ready for deployment with your app project setup.
  • Follow the initial CD Flutter Guide to setup the Fastlane folders in your Flutter project.

When you have completed all the prerequisites, you should have the following project structure with Fastlane folders in both iOS and Android folders:

- android
  - fastlane
    - Appfile
    - Fastfile
- ios
  - fastlane
    - Appfile
    - Fastfile

Note: In this guide I will use the basic Fastlane commands. It is highly recommended to use Fastlane with bundle. Read more about it in this Fastlane documentation.


Setting up the Fastfiles and environment

Now we will setup the Fastfiles for both iOS and Android. We will define lanes for deploying the app to the AppStore and PlayStore.

Android

  1. Open android/Gemfile and add the flutter version plugin we need to extract the version from the pubspec.yaml file:

    # ...
    source "https://rubygems.pkg.github.com/tianhaoz95" do
      gem "fastlane-plugin-flutter_version", "1.1.15"
    end
  2. Open android/fastlane/Appfile and set the path to your service account JSON file and the package name of your app:

    json_key_file("path/to/your/service-account.json")
    package_name("com.example.yourapp")

    You have created the service account JSON file in the CD Flutter Guide. Make sure to exclude the file from your git repository.

  3. Open android/fastlane/Fastfile and define the deploy lane (delete the existing content):

    default_platform(:android)
    
    platform :android do
      desc "Deploy a new version to Google Play"
      lane :deploy do
        version = flutter_version()
        upload_to_play_store(
          track: 'internal', # Can be 'internal', 'alpha', 'beta', 'production'
          skip_upload_metadata: true, # Skip uploading metadata
          skip_upload_images: true, # Skip uploading screenshots
          skip_upload_screenshots: true, # Skip uploading screenshots
          release_status: "completed", # Can be 'draft', 'completed', 'halted'
          aab: '../build/app/outputs/bundle/release/app-release.aab', # Path to your AAB file
          version_code: version["version_code"], # From pubspec.yaml
          version_name: version["version_name"] + version["version_code"],
        )
      end
    end

    More information about the upload_to_play_store action can be found here.

  4. Run bundle install in the android directory to install the required gems.

  5. Run fastlane supply init to initialize the PlayStore metadata. This will allow you to update the metadata like app description or screenshots without leaving your IDE. You can skip this step if you don't want to upload any metadata.

  6. Build the app with flutter build appbundle --release and make sure the AAB file is located in the path you defined in the Fastfile and to use the release signing keys. Follow the Android guide to set up release signing.

  7. Before deploying your first release you need to have at least one release already uploaded and published manually.

  8. Run fastlane deploy to start the lane and deploy your app to the PlayStore.

Depending on your setup, you might need to adjust the upload_to_play_store action to match your requirements.

Google Play Store Release Dashboard Play Store Deploy

iOS

  1. Open ios/Gemfile and add the Flutter version plugin again:

    # ...
    source "https://rubygems.pkg.github.com/tianhaoz95" do
      gem "fastlane-plugin-flutter_version", "1.1.15"
    end
  2. Open ios/fastlane/Appfile and define app the identifier, the Apple ID of your Apple developer account, and the team ID:

    app_identifier("com.example.yourapp")
    apple_id("[email protected]")
    itc_team_id("123456")
  3. Open ios/fastlane/Fastfile and define the deploy lane (delete the existing content):

    platform :ios do
      lane :deploy do
        pilot(
          skip_waiting_for_build_processing: true, # Skip waiting so we don't waste precious build time
          changelog: "This build was uploaded using fastlane",
          ipa: "../build/ios/ipa/flutter_github_actions.ipa" # Path to your IPA file
        )
      end
    end

    More information about pilot can be found in the Fastlane documentation. Depending on your setup you might need to use actions like gym or match to build the IPA file.

  4. Run bundle install in the ios directory to install the required gems.

  5. Run fastlane deliver init to initialize the AppStore metadata. With this you can update the metadata like app description or screenshots without leaving your IDE. You can skip this step if you don't want to upload metadata.

  6. Make sure you have already created an app in AppStore Connect.

  7. Depending on the account you are using you might need to further authenticate with an app-specific password. Refer to the Fastlane documentation for more information.

  8. Run fastlane deploy to start the lane and deploy your app to the AppStore.

Depending on your setup, you might need to customize the actions to suit your requirements. Also consider to use the actions.

AppStore TestFlight Builds Dashboard

AppStore Deploy

Setting up GitHub Actions

Now that Fastlane is set up and you have successfully run the lanes manually on your local machine, we can automate the deployment process using GitHub Actions.

Note: For simplicity, we will use a self-hosted runner in this guide. You can also use the GitHub hosted runners. Make sure to adjust the paths and the Fastfile accordingly and also add any necessary tools to the runner.

Now, let's automate these processes with GitHub Actions:

  1. In your GitHub repo, create a new directory .github/workflows.

  2. Create a new file in this directory, e.g. deploy.yml.

  3. We need to create some secrets. Go to your repository settings and add the following secrets:

    • STORE_PASSWORD: The password for your keystore file.
    • KEY_JKS: The base64 encoded keystore file.
    • SEC_JSON: The base64 encoded service account JSON file.
    • FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: The app-specific password for your Apple developer account.

    To encode the files you can use the following command:

    cat path/to/your/file | openssl base64

    Copy the output and add it as a secret.

  4. Add the following content to the deploy.yml file:

    name: Build and Deploy
    on:
      workflow_dispatch:
    
    jobs:
      build_android:
        concurrency:
          group: ${{ github.workflow }}-${{ github.ref }}-android
          cancel-in-progress: true
        name: Build and Deploy Android
        runs-on: self-hosted
    
        steps:
          - uses: actions/checkout@v4
          - uses: actions/[email protected]
            with:
              distribution: "zulu"
              java-version: "17"
          - name: Create Key properties file
            run: |
                cat << EOF > "./android/key.properties"
                storePassword=${{ secrets.STORE_PASSWORD }} 
                keyPassword=${{ secrets.STORE_PASSWORD }}
                keyAlias=upload
                storeFile=./key.jks
                EOF
          - name: Decode key file
            run: echo "${{ secrets.KEY_JKS }}" | openssl base64 -d -out ./android/app/key.jks
          - name: Decode sec json file
            run: echo "${{ secrets.SEC_JSON }}" | openssl base64 -d -out ./android/sec.json
          - uses: subosito/flutter-action@v2
          - run: flutter packages pub get
          - run: flutter build appbundle --release
          - name: Fastlane Action
            uses: maierj/[email protected]
            with:
              lane: deploy
              subdirectory: android
    
      build_ios:
        concurrency:
          group: ${{ github.workflow }}-${{ github.ref }}-ios
          cancel-in-progress: true
        name: Build and Deploy iOS
        runs-on: self-hosted
        env:
          FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
    
        steps:
        - uses: actions/checkout@v4
        - uses: subosito/flutter-action@v2
        - run: flutter packages pub get
        - run: flutter build ipa --release
        - name: Deploy iOS Beta to TestFlight via Fastlane
          uses: maierj/[email protected]
          with:
            lane: deploy
            subdirectory: ios

    Let me break down what this workflow does. There are two jobs, build_android and build_ios. Each job builds and deploys the app to the PlayStore and AppStore respectively. The jobs are manually triggered by the workflow_dispatch event. To build the Android app we need to create the key.properties file and decode the keystore and service account JSON file. Then we build the app bundle and run the Fastlane action with the deploy lane. The iOS job is simpler, we just build the IPA file and run the Fastlane action with the deploy lane.

  5. Adjust the flutter-version as per your project's requirements.

  6. Trigger the workflow manually by going to the Actions tab in your GitHub repository and selecting the Build and Deploy workflow. Click on the Run workflow button and select the branch you want to deploy.

  7. Sit back and watch the magic happen! ✨

    GitHub Actions

Whenever you want to deploy a new version of your app, just edit the version in pubspec.yaml, push your changes to the main branch and trigger the workflow manually. The workflow automatically will build and deploy your app to the AppStore and PlayStore automatically.

Build number

For some projects it makes sense to use the build number currently used on the respective platform. There are actions to retrieve the build number from the AppStore and PlayStore. You can use the app_store_build_number and google_play_track_version_codes actions to get the build number. You can then use this build number to set the version code in the Fastfile.

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. Time to sit back, relax, and let automation take care of the repetitive tasks.

Overview

We are still not done here. The next guide will cover how to automate the versioning, testing and of your app with Fastlane. Stay tuned! Happy coding! 🚀

About the Author

Dario Digregorio - Senior Flutter Developer

Dario is a passionate and innovative Senior Flutter Developer at NTT Data with a keen interest in crafting seamless user experiences using cutting-edge technology. You can find him on LinkedIn and GitHub.




Flutter CI/CD with Fastlane and GitHub Actions: Releases (Part 2)

Flutter CI/CD

Introduction

In the previous article, we saw how to setup 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 setup Fastlane and GitHub Actions.

Creating the Tag workflow

We will create a new workflow that will bump the pubspec.yaml version, create 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

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 because due to a limitation of GitHub. Follow this guide to create a token with the scope repo scope. 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. Tag
  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 the 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/[email protected]
            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/[email protected]
            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.

About the Author

Dario Digregorio - Senior Flutter Developer

Dario is a passionate and innovative Senior Flutter Developer at NTT Data with a keen interest in crafting seamless user experiences using cutting-edge technology. You can find him on LinkedIn and GitHub.