Starting a new Flutter app is quick. Getting its first build into someone else’s hands is not, and almost none of that time goes into the app itself.

Jump to project template

What a new project actually needs

Here is what a new Flutter project actually needs before anyone else can open it, assuming you want three build flavours so that dev, staging and production do not share a database:

Google Cloud, for a project per flavour, billing linked, the right APIs enabled, service accounts created and IAM bindings granted. Firebase, attached to each of those projects. Codemagic, with the app registered and around twenty secrets uploaded. Sentry, for a project and a DSN per flavour. OneSignal, for an app per flavour plus a dedicated Firebase Cloud Messaging service account with exactly the right roles. Shorebird, initialised and authenticated. App Store Connect, for a registered bundle identifier, a distribution certificate and a provisioning profile. Google Play Console, for an application entry that will accept a first upload.

That is a lot of web consoles. Done by hand it is a one to two day checklist, and I have never once got through it without a mistake, which made me think how I could automate it.

The mistakes are the real problem, because almost none of them fail loudly. A bundle identifier with a typo does not stop anything: it quietly creates a second, wrong app record. A service account with the right name and the wrong role works fine until you realise an integration is broken. This steals your time, and it kills your motivation.

The part I underestimated

Store metadata deserves its own paragraph. Names, subtitles, descriptions, keywords, categories, support URLs, age ratings, and screenshots in several sizes for phones and tablets, for two stores, for three flavours, and again for every language you support. Doing it by hand once is tedious. Doing it again after you change a screen is worse, because now it is tedious and you also have to remember which of the twenty images changed. This is exactly the kind of work that should never involve a human hand.

Fastlane is excellent, but it may still take you a week

Fastlane deserves its reputation. It is what makes app registration, store metadata, screenshots, Play internal releases and Test Lab runs automatable at all, and the template leans on it heavily on both platforms.

Signing I ended up doing another way. Since the builds run on Codemagic anyway, their CLI tools do it: fetch the signing files from App Store Connect, add the certificate to the keychain, patch the profiles into the Xcode project. Fewer moving parts than keeping a certificate repository of my own in sync, and one less thing to explain to a future me.

What no tutorial conveys is how many attempts it takes to get a lane working end to end. Signing has a lot of parts, they interact, and the error messages tend to describe the symptom rather than the cause. My favourite genre is the one that tells you the profile does not match, without mentioning which of the four plausible identifiers it compared.

Now add remote builds. I did not own a Mac when I started, so every iOS attempt ran on Codemagic. That turns a two minute local edit-and-retry loop into something much slower: push, wait for a machine, wait for the build, read the log, guess, repeat. You learn very quickly to make each attempt count, which is a good discipline and a terrible way to spend a day.

Codemagic’s free tier is genuinely generous for an independent developer, and it was the only way I could produce an iOS build with my Linux-only setup.

One thing you can’t do, as I found during a long trail of failures, that you can’t reliably update iOS flavours and permissions without Xcode. Even with Claude or Flutter packages. Eventually I gave up and bought an old 2014 Mac Mini, second hand and slow, and retrofitted it with an SSD. But this is a topic for another post.

What the template does now

All of it lives in birdcorner_app_template, public domain, use it however you like.

You give it a project name and a domain. It derives everything else, including the bundle identifiers, the Android package directories, and a hashed slug that fits Google Cloud’s thirty-character project ID limit without collisions. Then it provisions every service listed above, across three flavours, in one pass, and finishes by pushing the first build to TestFlight. Thirty to sixty minutes.

One caveat, and it is not a small one: the thirty minutes do not include getting ready. The accounts have to exist and be paid for before you start. That means an Apple Developer Program membership, a Google Play developer account, a Google Cloud billing account, and accounts at Codemagic, Sentry, OneSignal and Shorebird. Your machine needs its own preparation too: the Flutter SDK, Google Cloud and Firebase CLI, Fastlane, the GitHub CLI, and each of them authenticated with credentials stored where the scripts expect to find them, outside the repository. None of that is automated, some of it involves waiting on Apple, and doing it the first time is an afternoon.

What the automation removes is everything after that point, which is also the part you repeat for every new app and the part where mistakes hide.

Every step is idempotent. Each one checks whether the thing already exists: GCloud project or the app listing in a store. If it’s there, there’s nothing to do, and the script continues.

There is a rollback script. It tears down what was provisioned in case you decide to choose another name. Caveat: you can’t permanently delete some artifacts, for example, App Store listings can only be manually archived. Also, tearing down a GCloud project can take weeks.

The application side of the template is opinionated in the ordinary ways: Riverpod, go_router, freezed models, localisation wired up from the start, and a test layout covering unit, widget, golden and integration tests, the last of those running on real devices in Firebase Test Lab. There is also a Codemagic configuration with separate workflows for full TestFlight builds and Shorebird patches.

Status: work in progress but already useful

It is a work in progress. The provisioning path I have run many times; the edges, particularly around store metadata, are still being smoothed. Some of it encodes my preferences rather than any universal truth.

But it is already the thing I reach for when I start a new app. Hope it’ll save you time you spend on building the thing rather than admin.