How to distribute Flutter Desktop app binaries using GitHub Actions

Angelo Cassano
7 min readFeb 19, 2022

A few weeks ago Flutter team officially announced Flutter 2.10, moving Flutter Desktop for Windows from beta to stable.

For some time I had an idea in my mind: I wanted to create a desktop application able to run on the three most known and used operative systems to simplify the process of gathering a macOS recovery image.

Since I’m fluent with Dart and Flutter, I decided to stick with it and leave Electron and Javascript on my back, moreover Flutter Desktop for Windows available on the stable channel gave me a further push to follow this path.

The App: MacRecoveryX

It took no more than a few days to develop my new app. MacRecoveryX, that’s the name I gave to it it’s a very simple tool. It allows you to choose the version of macOS you wish to download, define where to place the two recovery files (the dmg image system and its chunklist), and start the process to download these files.

MacRecoveryX — welcome page

Once I finished developing my app, I needed to find a way to deploy it to the world. According to the official Flutter documentation, you can distribute your Flutter apps bundling an MSIX file for Windows, Snap for Linux, and Apple Store for macOS. I thought it was too overkill for a simple tool.

The source code

As a fervent supporter of the open-source world, I decided to version the MacRecoveryX source code on GitHub.

That wasn’t enough. MacRecoveryX needs a Flutter SDK to allow users to generate artifacts and run them: I needed a way to generate artifacts after a push (a tag would be better) for each OS target, compress them into a zip file and then release them in the repository. GitHub Actions was the key to success.

GitHub Actions pipeline

To trigger a GitHub actions pipeline we need to create a .yml file under the .github/workflows folder at the root of our project.

Since we need to deploy a Flutter app, we will name it Flutter CI. We also need to build our app for the three main targets: Windows, Linux, and macOS. We will split the build flow into three different jobs. The scripts will run respectively on Windows, Ubuntu, and macOS.

name: Flutter CI

on: push

jobs:
build-and-release-linux:
runs-on: ubuntu-latest

build-and-release-windows:
runs-on: windows-latest

build-and-release-macos:
runs-on: macos-latest

The build process

Now let’s focus on the build process, we need to:

  • Check out our source code from GitHub — with actions/checkout@v2
  • Choose the right version of Flutter to build our project — with subosito/flutter-action@v1
  • Install a bunch of dependencies to compile our artifacts (some of them are mentioned here)
  • Retrieve the flutter project dependencies using flutter pub get
  • Generate some intermediate files using build_runner
  • Enable the target desktop configuration
  • Build the artifact
  • Compress the artifact and its files in a zip file — with thedoctor0/zip-release@master
  • Deploy the zip file as a new release — with softprops/action-gh-release@v1
    steps:
- uses: actions/checkout@v2
- uses: subosito/flutter-action@v1
with:
channel: 'stable'
flutter-version: '2.10.0'
- name: Install project dependencies
run: flutter pub get
- name: Generate intermediates
run: flutter pub run build_runner build --delete-conflicting-outputs
- name: Enable windows build
run: flutter config --enable-windows-desktop
- name: Build artifacts
run: flutter build windows --release
- name: Archive Release
uses: thedoctor0/zip-release@master
with:
type: 'zip'
filename: MacRecoveryX-${{github.ref_name}}-windows.zip
directory: build/windows/runner/Release
- name: Windows Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: build/windows/runner/Release/MacRecoveryX-${{github.ref_name}}-windows.zip

Enabling Desktop support

To enable Windows Desktop support we will execute:

flutter config --enable-windows-desktop

To enable Linux Desktop support we will execute:

flutter config --enable-linux-desktop

To enable macOS Desktop support we will execute:

flutter config --enable-macos-desktop

Building the app

Once enabled desktop support, we need to build our app with the following instructions.

To build a Windows Desktop app we will execute:

flutter build windows --release

To build a Linux Desktop app we will execute:

flutter build linux --release

To build a macOS Desktop app we will execute:

flutter build macos --release

Zipping the artifacts

Every build will create an executable and a bunch of support files like libraries and assets. Every target will create the artifacts in a different path.

To create a zip artifact for Windows we will run the following step:

- name: Archive Release
uses: thedoctor0/zip-release@master
with:
type: 'zip'
filename: MacRecoveryX-${{github.ref_name}}-windows.zip
directory: build/windows/runner/Release

To create a zip artifact for Linux we will run the following step:

- name: Archive Release
uses: thedoctor0/zip-release@master
with:
type: 'zip'
filename: MacRecoveryX-${{github.ref_name}}-linux.zip
directory: build/linux/x64/release/bundle

To create a zip artifact for macOS we will run the following step:

- name: Archive Release
uses: thedoctor0/zip-release@master
with:
type: 'zip'
filename: MacRecoveryX-${{github.ref_name}}-macos.zip
directory: build/macos/Build/Products/Release

Releasing the assets

Once done, we need to upload these assets as a set of release artifacts.

To release the Windows zip file we will run the following step:

- name: Windows Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: build/windows/runner/Release/MacRecoveryX-${{github.ref_name}}-windows.zip

To release the Linux zip file we will run the following step:

- name: Linux Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: build/linux/x64/release/bundle/MacRecoveryX-${{github.ref_name}}-linux.zip

To release the macOS zip file we will run the following step:

- name: macOS Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: build/macos/Build/Products/Release/MacRecoveryX-${{github.ref_name}}-macos.zip

The completed script

In the end, the script will look like this:

name: Flutter CI

on: push

jobs:
build-and-release-linux:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: subosito/flutter-action@v1
with:
channel: 'stable'
flutter-version: '2.10.0'
- name: Install dependencies
run: sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev
- name: Install project dependencies
run: flutter pub get
- name: Generate intermediates
run: flutter pub run build_runner build --delete-conflicting-outputs
- name: Enable linux build
run: flutter config --enable-linux-desktop
- name: Build artifacts
run: flutter build linux --release
- name: Archive Release
uses: thedoctor0/zip-release@master
with:
type: 'zip'
filename: MacRecoveryX-${{github.ref_name}}-linux.zip
directory: build/linux/x64/release/bundle
- name: Linux Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: build/linux/x64/release/bundle/MacRecoveryX-${{github.ref_name}}-linux.zip

build-and-release-windows:
runs-on: windows-latest

steps:
- uses: actions/checkout@v2
- uses: subosito/flutter-action@v1
with:
channel: 'stable'
flutter-version: '2.10.0'
- name: Install project dependencies
run: flutter pub get
- name: Generate intermediates
run: flutter pub run build_runner build --delete-conflicting-outputs
- name: Enable windows build
run: flutter config --enable-windows-desktop
- name: Build artifacts
run: flutter build windows --release
- name: Archive Release
uses: thedoctor0/zip-release@master
with:
type: 'zip'
filename: MacRecoveryX-${{github.ref_name}}-windows.zip
directory: build/windows/runner/Release
- name: Windows Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: build/windows/runner/Release/MacRecoveryX-${{github.ref_name}}-windows.zip

build-and-release-macos:
runs-on: macos-latest

steps:
- uses: actions/checkout@v2
- uses: subosito/flutter-action@v1
with:
channel: 'stable'
flutter-version: '2.10.0'
- name: Install project dependencies
run: flutter pub get
- name: Generate intermediates
run: flutter pub run build_runner build --delete-conflicting-outputs
- name: Enable macOS build
run: flutter config --enable-macos-desktop
- name: Build artifacts
run: flutter build macos --release
- name: Archive Release
uses: thedoctor0/zip-release@master
with:
type: 'zip'
filename: MacRecoveryX-${{github.ref_name}}-macos.zip
directory: build/macos/Build/Products/Release
- name: macOS Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: build/macos/Build/Products/Release/MacRecoveryX-${{github.ref_name}}-macos.zip

The final result

Every time we will push into our repository, a new workflow will be triggered. Furthermore, if we push a tag, the pipeline will also proceed to create a release and upload a custom set of artifacts according to the OS target.

The release section will look like this:

Conclusions

It’s often unnecessary to build and deploy your Desktop application using the Windows Store, Snap, or the Apple Store. You can simply prepare a GitHub Actions pipeline to create a set of artifacts, zip the files, and release the zip files in the repository.

--

--