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!)For people coming across this tweet: @HydraulicDev was 100% worth the money!
— Verry 💙 (@verry_codes) March 8, 2023
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
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.