<FlutterSolution/> flutter · dart · clean architecture

Flutter Prep #29 CI/CD & Release Engineering for Flutter: From Merged PR to Production Rollout

CI/CD & Release Engineering for Flutter: From Merged PR to Production Rollout

Why writing code is only half the job, and how to prove you can actually get it safely into users' hands.

1. Why This Matters for "Just Flutter" Roles

You will be asked to walk through this exact pipeline, start to finish, in more interviews than you'd expect. A candidate who can only talk about writing Dart code, but freezes when asked how that code actually reaches users safely, is missing half of what a senior engineer owns.

Building a shopping app that works on your Macbook simulator is one thing. Ensuring that a merged pull request doesn't accidentally break the production build, passing it through automated tests, signing the binary securely, appeasing Apple's review reviewers, and rolling it out safely without tanking the company's revenue stream requires Release Engineering. In this post, we will frame these concepts around a single connected pipeline.

2. The Full Pipeline Narrative

An interviewer doesn't want a bulleted list of tools. They want a narrative. If your team is shipping a new checkout feature for a shopping app, here is the lifecycle of that code:

The Release Lifecycle
PR Opened
Unit/Widget tests on Linux (Cheap, Fast)
PR Merged
Code enters main branch
Nightly Build
Integration tests on Device Farm (Expensive)
Staged Rollout
Release to 1% of users
Monitoring Gate
Check Crash & ANR rates
100% Rollout
General Availability

Notice the specific choices here: Unit and Widget tests run on every push because they are fast and can run on cheap Linux runners. Full Integration tests (driving the UI on real devices) are expensive and slow, so they are batched into a nightly run. The rollout isn't a push-button event; it's a staged process gated by real metrics.

3. The Toolchain: Flavors, Signing, and Fastlane

To execute the narrative above, you need a standard toolchain (commonly GitHub Actions, Codemagic, or Bitrise). But configuring the CI runner is only the first step.

Build Flavors

Your CI needs to produce a build for QA and a build for Production from the exact same codebase. You do this using Build Flavors (Android) and Build Configurations/Schemes (iOS), often injected via Dart environment variables (--dart-define). This allows the same code to use api.staging.store.com with the bundle ID com.store.app.dev for QA, while using api.store.com and com.store.app for production, without duplicating Dart code.

Code Signing & Fastlane Match

Neither Apple nor Google will accept a binary unless it is cryptographically signed proving it came from the legitimate publisher. iOS uses Certificates and Provisioning Profiles; Android uses Keystores.

At a team scale, manually emailing certificates and passwords back and forth is a security and operational nightmare. This is exactly why Fastlane Match exists. Match encrypts your certificates and profiles, stores them in a private Git repository or cloud storage, and allows any authorized developer (or your CI machine) to securely pull and sync them with a single command. Understanding Match proves you understand team-scale development.

4. Melos-Based Selective CI

If you followed the modularization strategies from earlier in this series, your shopping app is now a monorepo containing dozens of packages. Running tests and builds for the entire app on every single PR push wastes massive amounts of CI compute time (and money).

Because Melos understands the dependency graph of your monorepo, it can detect which specific packages were changed in a given PR.

PR modifies: core_network
Package: core_network
(Modified)
Package: feature_checkout
(Depends on core)
Package: feature_profile
(Independent)

Melos will run tests for core_network and feature_checkout (because it depends on the changed code), but will completely skip feature_profile. This selectively targets your CI, drastically lowering feedback loop times for developers. This is the direct DevOps payoff for good architectural modularization.

5. Versioning vs. Build Numbers

A common point of confusion is how app versioning actually works for store submissions. There are two distinct values:

yaml (pubspec.yaml)
# version: [semantic_version]+[build_number]
version: 2.4.1+87
  • Semantic Version (2.4.1): The human-readable string the user sees in the App Store. Bumped manually when a release is planned based on semantic rules (major/minor/patch).
  • Build Number (87): The integer the app stores use internally to distinguish uploads. It must monotonically increase with every upload.

You should never rely on developers remembering to bump the build number manually in the pubspec.yaml. A mature CI pipeline automatically increments and injects the build number (often using the CI runner's internal build ID) during the build step using the --build-number flag.

6. The Reality of Store Reviews

If you have only ever run apps on an emulator, this is where you will get caught out. Deploying a binary does not mean users get it immediately. You must navigate store review policies.

🍏 App Store (iOS)
  • Review Time: Typically 12-48 hours. Can be expedited for critical bug fixes, but not guaranteed.
  • Common Rejections: Privacy manifest issues. Apple increasingly requires apps to explicitly justify why they need access to specific APIs (like user defaults or disk space) in a privacy manifest file.
  • Business Rejections: Trying to bypass Apple's In-App Purchase 30% cut for digital goods by linking out to a website is an instant rejection.
πŸ€– Play Console (Android)
  • Pre-Launch Report: Google automatically runs your uploaded binary through Firebase Test Lab devices on various Android versions, catching basic startup crashes before human review even begins.
  • Review Time: Highly variable. Established accounts might see updates approved in hours; newer accounts or major permissions changes can take days.
  • Staged Rollouts: Play Console allows granular control to halt or resume a rollout based on real-time data.

A candidate who has actually dealt with a rejection—say, getting rejected because a third-party analytics SDK didn't properly declare data collection in the privacy manifest—has a highly credible, senior story to tell.

7. Monitoring as a Gate, Not an Afterthought

The pipeline does not end when the app hits the store. The entire purpose of a Staged Rollout (e.g., releasing to 1%, then 5%, then 20%) is to limit the blast radius if a critical bug slips through testing.

However, staged rollouts only work if you are actually looking at the data before advancing the percentage. Monitoring is a formal blocking gate.

Production Rollout: V2 Checkout
Gate: Crash-Free User Rate > 99.5%

You specifically monitor the Crash-Free User Rate (not just the raw crash rate, as one user crashing 100 times skews the data) and the ANR (Application Not Responding) Rate. If you hit 5% rollout and your Crash-Free User rate drops from 99.9% to 98.0%, you halt the rollout. The CI pipeline stops here. The fix requires opening a new PR, merging it, and starting the pipeline entirely over with a new build number.

🧠 Interviewer's Follow-Up

Q: What specific metrics would make you halt a staged rollout partway through?
A: A drop in Crash-Free User rate below our SLA threshold (e.g., 99.5%), a spike in Android ANRs flagged in the Play Console, or a sudden, statistically significant drop in a core business metric like checkout conversion rate.

8. Senior-Level Sample Answer

If an interviewer asks: "Walk me through what happens from the moment a developer merges a PR for the new checkout flow to it reaching 100% of our users."

"When the PR is opened, our CI runs fast unit and widget tests, utilizing Melos to only test the feature_checkout package and its dependents. Once merged, the code is picked up by our nightly build, which runs heavier UI integration tests on a device farm. For the release build, Fastlane handles pulling our certificates via Match and building the flavored production binaries, injecting an auto-incremented build number. We upload these to TestFlight and the Play Console. Once approved by store review, we initiate a staged rollout at 1%. We hold there for 24 hours, strictly monitoring Crash-Free User rates and ANRs in Datadog or Crashlytics. If those metrics remain healthy and clear our thresholds, we advance the rollout gate to 10%, 50%, and finally 100% general availability."
✅ Quick Self-Check

Before moving to the next post, answer this unscripted: Why does a team need both a semantic version string (like 1.4.2) and a separate integer build number (like 87) for a mobile release?

Official Resources


Written by Flutter Solution · Covering the Flutter & Dart ecosystem from Mumbai, India · fluttersolution.com
Nachiketa Pandey
// written by

Nachiketa Pandey

Senior Flutter Developer · Cross-Platform & Fintech Applications

Building production Flutter apps at Kotak Neo — clean architecture, BLoC, agentic AI integrations, and fintech-grade security. Sharing everything I learn, one post at a time.

Flutter & Dart Clean Architecture BLoC Firebase FinTech
View Full Portfolio

Comments

Nachiketa
Meet Author// tap to open