Flutter support

You can now easily package and update Flutter apps with Conveyor, a tool that makes desktop distribution as easy as it is on mobile (or easier!). Distributing your Flutter app with Conveyor not only gives you useful features like online updates, but also a really simple config and workflow. Let’s dive in and see just how fast we can create a new app and get it online.

(N.B. Conveyor is free for open source projects!)

What is Flutter?

If you haven’t heard of Flutter before, don’t worry: it’s a fast rising star of the mobile world and now Google have brought it to the desktop. Flutter is a fast runtime and graphics stack that uses the Dart programming language. By writing in Dart you can make apps that share all their code between iOS, Android and now also all three major desktop platforms. This gives you high levels of code sharing whilst hitting a solid 60fps GUI at all times, cool features like hot reload and a high degree of developer productivity.

Why use Conveyor?

Flutter is great, but distributing your app requires you to master complicated native tooling and processes. You’ll also have to figure out a solution for online updates. Conveyor eliminates the pain of distributing to the desktop, perfectly complimenting Flutter’s enjoyable developer experience.

Everything can be specified in one simple config file that’s unified between platforms. You can build packages for every OS from any OS, software update is integrated for you, signing and notarization is handled (even if you aren’t using a Mac), and it supports a web-like update mode in which updates are checked for and applied immediately on every launch. It even generates a download page for you! You get everything you need to distribute outside the app stores (if you want to release to the stores, support for that can be voted for here and here).

Although the config abstracts you from the operating systems, it doesn’t stop you using OS-specific features. Parts of the config are mapped to platform specific metadata Info.plist on macOS, the AppxManifest.xml on Windows and the .desktop and .service files on Linux.

Finally, for Windows installs Conveyor has a party trick: the installer will skip downloading file blocks if the user already has them, and will even hard-link files between apps if they’re identical. This feature of Windows works across different apps from different vendors, as long as they’re installed either via Conveyor or from the store. It’s fantastic for Flutter because the bulk of the download is the runtime which can be shared between apps, leading to nearly “instant on” installs.

See a sample app

The Flutter starter app is packaged and ready to download. You can view the source (check out the conveyor.conf file), and try downloading it. This demo shows both how to write the config and also how to set up GitHub Actions to compile the app on Windows, macOS and Linux.

Take the tutorial

We have a one-page tutorial that takes you through everything from generating a new project, compiling it, setting the icon, releasing using GitHub Releases (or any other website), buying code signing certificates and signing/notarizing the results. You’ll also learn how to extract config from your pubspec.yaml file, so you don’t need to duplicate things.

An example config

Here’s what the config looks like for a typical Flutter app:

include required("/stdlib/flutter/flutter.conf")

pubspec {
  include required("#!yq -o json pubspec.yaml")

We start by telling Conveyor this app uses Flutter, and that we want to have access to the data in the pubspec.yaml file. Here we use the yq tool to convert it to JSON, so we can import it and use as a source of substitutions later. The config file is written in HOCON, a superset of JSON syntax that’s designed for config files.

Now we can define our package metadata:

app {
  display-name = My Project
  fsname = my-project
  rdns-name = com.example.MyProject
  vendor = SuperOrg
  version = ${pubspec.version}
  description = ${pubspec.description}  

The fsname is used for directories, file names etc. In simple cases like this Conveyor can infer the display name from the fsname, so there’s no need to specify the display-name key unless you have special capitalization.

Next up, online updates.

  site.base-url = "https://example.com/downloads"
  updates = aggressive

Apps will check for new versions at example.com/downloads and they’ll check “aggressively” i.e. every single time the app is started. If a new version has been released it’ll be downloaded and installed without any user prompts or interactions, and the new version of the app will then start up. This mode gives you a web-app like update experience, at the cost of slightly slower startup. On Windows updates will still download and apply in the background even if the app isn’t running at all, so in many cases your users might never see an update apply.

Delete the updates = aggressive line to use the default update mode. In this mode there’s no startup check. Your app will be updated Chrome-style on Windows, and the user will be offered the upgrade on macOS (they can then ask the app to update in the background whilst the app runs). On Linux an apt repository is generated regardless of mode and the package will configure apt when installed.

Now we locate the compiled binaries for each platform. Conveyor can cross-build packages, so you don’t need to run it on each target. Just tell it where to find the binaries:

  windows.amd64.inputs += build/windows/runner/Release
  linux.amd64.inputs += build/linux/x64/release/bundle
  mac.inputs += build/macos/Build/Products/Release/${pubspec.name}.app

… but you can also use URLs to download files from CI, like in the sample app …

  windows.amd64.inputs += ${ci-artifacts-url}/build-windows-amd64.zip

  linux.amd64.inputs += {
    from = ${ci-artifacts-url}/build-linux-amd64.zip
    extract = 2

  mac.amd64.inputs += {
    from = ${ci-artifacts-url}/build-macos-amd64.zip
    extract = 2

  mac.aarch64.inputs += {
    from = ${ci-artifacts-url}/build-macos-aarch64.zip
    extract = 2

The extract = 2 is to work around a limit in GitHub Actions.

For private CI you might need to authenticate by setting extra HTTP headers, and so you can do that too.

Finally, we can run conveyor make site and get the files to upload.

Take a look at what we get! The download page will detect your user’s OS and CPU architecture, giving them a big green download button. If you’re self-signing it’ll also contain instructions on how to bypass GateKeeper and install direct from the terminal, which is useful for test builds, student projects, open source apps and so on.

To release an update we just change the version number in our pubspec.yaml, rebuild and re-upload.

Code signing

Getting rid of security warnings is easy. The first time you generate a package Conveyor will create a new private “root key” for you, along with Certificate Signing Request files (CSRs). It’s represented as words, so you can back it up with pen and paper. The CSR files can be uploaded to Apple/a Windows certificate authority and swapped for real signing certificates, which can then be added to your source tree and used to sign. Or you can of course use your existing keys and certificates, including HSMs.

Useful extras

An example of a synthetic icon

Conveyor handles icons for you: it can convert images to the native formats, use SVG or PNG as the input, automatically frame SVGs onto an Apple-compliant rounded rectangle, edit the embedded executable icons in Windows and even synthesize an icon from scratch (example on the left).

Customizing the default icon is easy:

app {
    icons {
        label = "FD"
        gradient = "aqua;navy"

And finally …

  • Deep linking is easy, just set the app.url-schemes key.
  • You can make your Windows app start at login, which is good for kiosk apps.
  • Windows network admins will find it easy to deploy your app across their network using InTune and Active Directory, as Conveyor conforms to Microsoft’s standards.
  • You’ll automatically get bundled copies of the Microsoft Visual C++ runtime libraries that Flutter needs on Windows.
  • There’s no lock-in. Conveyor is generating open formats and using open source frameworks, so you can replicate its output on your own with enough scripting work.

There are several choices for bringing a Flutter app outside of mobile, but distributing to the desktop is easy and ensures your users get the best possible feature set and performance. Why not try it today? You can download it and get started for free.