Write Scalable Circle CI Workflows for Flutter, The Orb Way
6 min read

Write Scalable Circle CI Workflows for Flutter, The Orb Way

Write Scalable Circle CI Workflows for Flutter, The Orb Way

It's a challenge to maintain all the commands, jobs, workflows, and everything else in a single Circle CI config.yml or config file. Luckily, there's an alternative way to make it more human-readable for the developer teams.

An example of a simple CI/CD workflow for Flutter

In smaller projects or teams, creating and maintaining your CI/CD workflows in a single config file is still manageable. Circle CI provides a simple syntax in which you can use different environments or executors, and write commands and jobs that can be reused in different workflows.

An example of a complex CI/CD workflow for Flutter

But, if you add more Flutter build flavors and settings, write more kinds of tests, deploy on various app stores like Apple or Google Play Store, or work with a larger team, it's challenging to write all of these in a single file. I've seen some going over a thousand lines!

Before we write in the orb way, let's see how we set up Flutter's counter app in CircleCI.

Use CircleCI workflows for Flutter

In Flutter, you need a main workflow that runs on every code change, either for a pull request or commits sent to the main branch. Your main workflow for integration contains setting up the project SDK and downloading package dependencies, checking lint, testing, and building the code.

Create a new Flutter project

I'm using Flutter's default counter app as an example because it's simple enough that it should give you a clear picture of how you can use the techniques that you use here in your projects whether your project is simple or complex.

To create a new Flutter app, run:

flutter create circleci_flutter_demo

Add Circle CI to your Flutter project

Create a folder named .circleci in the project's root directory:

md .circleci

Create a file named config.yml:

touch .circleci/config.yml

Then, add the following at the topmost part of the file:

orbs:
  flutter: circleci/flutter@1.1.0
version: 2.1

executors:
  android:
    docker:
      - image: 'cimg/android:2022.06'

The orbs parameter contains the reusable packages, or orbs, that we need for our workflows. circleci/flutter is an example of an orb, it includes the basic commands and instructions for your Flutter projects.

The executors are the environments or platforms where our workflows are running.

Next, add the following commands:

commands:
  analyze:
    description: >-
      Run lint checks Flutter code base on the rules set on
      `analysis_options.yml`
    steps:
      - run:
          command: flutter analyze .
          name: Analyze
  build-android:
    description: Builds APK for Android
    steps:
      - run:
          command: flutter build apk
          name: Build APK
  format:
    description: Run checks if Flutter code is formatted
    steps:
      - run:
          command: >-
            flutter format --set-exit-if-changed . || { echo 'Format check
            failed'; exit 1; }
          name: Analyze
  test:
    description: Runs the tests of your Flutter app
    steps:
      - run:
          command: flutter test
          name: Test

These commands can be reused on different jobs.

Next, add the following jobs:

jobs:
  build-android:
    environment:
      FLUTTER_CHANNEL: stable
      FLUTTER_VERSION: 3.0.1
    executor: android
    steps:
      - checkout
      - flutter/install_sdk_and_pub:
          flutter_version: 3.0.1
      - build-android
  tests:
    environment:
      FLUTTER_CHANNEL: stable
      FLUTTER_VERSION: 3.0.1
    executor: android
    steps:
      - checkout
      - flutter/install_sdk_and_pub:
          flutter_version: 3.0.1
      - format
      - analyze
      - test

Jobs can run in different environments and can be a combination of both inline or reusable commands. Jobs can be reused on different workflows.

Last, add the following workflows:

workflows:
  main:
    jobs:
      - tests
      - build-android:
          requires:
            - tests

Create your project's repo on Github

Circle CI integrates seamlessly with Github and other platforms like Bitbucket. You can commit your local changes and upload your Flutter project to the new Git repository.

Configure project on Circle CI

In your projects dashboard, select the repository of your Flutter project, then click Set Up Project.

Projects Dashboard on Circle CI

You should see a successful workflow run.

The config already ends up having 67 lines of code. Take note, that this workflow doesn't include integration tests, iOS builds, and deployment yet.

Let's write a config file like creating orbs and add more workflow steps.

Write The Orb Way

Orbs in Circle CI are reusable packages shared by other developers to help you speed up project development. You can explore the orbs published here.

To make it manageable for the developer to write an orb, Circle CI provides an orb starter template:

.circleci
└── src
	├── commands
	├── executors
	├── jobs
	├── workflows
    	└── orb.yml
└── config.yml

The config.yml file here is generated using orb packing. Orb packing is a tool available in the Circle CI local CLI.

You can use the orb template and the orb packing tool for the workflows of your Flutter projects too — it's the orb way.

Prepare the template source directory

The template source directory of your Circle CI configuration makes writing workflows in a human-readable way, files are isolated, and become more manageable in the long run.

@orb.yml

In the root of the template source directory, create a file named orb.yml:

version: 2.1
orbs:
  flutter: circleci/flutter@1.1.0

This is where you add orbs your project uses.

Create commands

Create the following files inside the commands directory:

analyze.yml

description: Run lint checks if Flutter code is formatted
steps:
  - run:
      name: Analyze
      command: >-
        flutter format --set-exit-if-changed . || { echo 'Lint check failed';
        exit 1; }

build_android.yml

description: "Builds APK artifact for Android"
steps:
  - run:
      name: "Build APK"
      command: flutter build apk

format.yml

description: "Run checks if Flutter code is formatted"
steps:
  - run:
      name: "Analyze"
      command: |- 
        flutter format --set-exit-if-changed . || { echo 'Format check failed'; exit 1; }

test.yml

description: "Runs the tests of your Flutter app"
steps:
  - run:
      name: "Test"
      command: |-
        flutter test

Create executors

Create the following inside the executors directory:

android.yml

docker:
  - image: cimg/android:2022.06

macos.yml

macos:
  xcode: 13.4.1

default.yml

docker:
  - image: cimg/base:stable

Create jobs

Create the following inside the jobs directory:

tests.yml

executor: android
steps:
  - checkout
  - flutter/install_sdk_and_pub:
      flutter_version: 3.0.1 
  - format
  - analyze
  - test  

build-android.yml

executor: android
steps:
  - checkout
  - flutter/install_sdk_and_pub:
      flutter_version: 3.0.1
  - build-android

build-ios.yml

executor: macos
steps:
  - checkout
  - flutter/install_sdk_and_pub:
      flutter_version: 3.0.1
  - flutter/install-pod-ios
  - build-ios

Create workflows

Create the following inside the workflows directory:

jobs:  
  - tests
  - build-android:
      requires:
        - tests
  - build-ios:
      requires:
        - tests

Pack the source directory

To generate the config.yml file, you'll need to first install the Circle CI local CLI tool.

For macOS with Brew:

brew install circleci

For Windows with Chocolatey:

choco install circleci-cli -y

See local CLI guide.

Next, pack the source directory:

cd .circleci && circleci config pack src > config.yml

Last, validate the configuration:

circleci config validate

If you're using GNU Make, you can add this rule:

.PHONY: pack
pack:
	circleci -h >/dev/null 2>&1 || { echo >&2 "Install the circleci cli with brew install"; }
	cd .circleci && circleci config pack src > config.yml
	circleci config validate

Now, you can pack your source directory like:

make pack

Read how you can use GNU Make for Flutter projects.

Furthermore, you can also create shell scripts or bat executables.

See the demo project here.

It's a wrap!

Writing your workflows Circle CI is made human-readable with orb packing. Your workflow configuration is split up into different files which means fewer merge conflicts, files have specific responsibilities and each has fewer lines of code.

There are other concepts like reusable config wherein you set parameters and reuse blocks of commands. The reusable config feature compliments orb packing in making your workflows more manageable.

Let me know what you think at hi@joshuamdeguzman.com.