Pink Room Blog
Engineering
Pink Room Way

How We Ship Mobile Apps Without Breaking Production

The quality checkpoints, from your first local commit to a phased rollout, that keep bugs out of the stores and you out of emergency hotfixes.

Web taught a generation of engineers that a bad deployment is one rollback away. Mobile grants no such mercy. Once a binary is in review, you’re on the store’s clock, not yours. A one-line mistake can sit in front of users for the better part of a day while the fix crawls through review on each platform.

‍

That asymmetry is the defining constraint of mobile delivery. It means the entire game is moving bugs as far left as possible: catching them on your machine, in the pipeline, and in review, long before they ever reach a phone. It also means a release can’t rest on any one person being sharp on any one evening. The step most likely to be skipped is always the boring one, at the end of a long day, on the change you were sure about.

‍

At Pink Room, we build mobile products for fintech, healthcare, and other domains where “mostly working” isn’t a thing. Across a lot of releases, we’ve settled on a way of shipping that treats a release not as a single moment but as a pipeline of checkpoints, each one standing in front of a specific risk. Here’s how we think about it.

‍

There’s no “one-size-fits-all”, but three things are non-negotiable

There is no single deployment process that's right for every project, or every team. A weekend side-project and a regulated banking app don't need the same ceremony, and neither do a solo developer and a ten-person team spanning seniors and juniors. Pretending otherwise just slows good teams down. The right process flexes with three things: the risk level, the shape of the project, and the team shipping it.

‍

That said, a few checkpoints are mandatory on everything we ship, no exceptions:

  • Automated tests exist. Not “we’ll add tests later”. Some automated safety net has to be in place. What kind and how much is a project decision; whether they exist is not.
  • At least two environments. Every project runs a minimum of two, typically production and a staging/pre-production twin, so nothing gets validated for the first time in front of real users.
  • Code review by one or two people. Every change into the main branch is read by at least one other human. It's the most effective bug-catcher we have and it does more than catch bugs. It's also one of the best ways we share knowledge across the team and learn from each other. It's never optional.

‍

Everything else in this article is a menu. These three are the meal.

‍

An exercise before you read on

Before you go further, stop and map your own project.

Take the pipeline below and, stage by stage, ask two questions: which of these checks does your project actually need, and which can you safely adapt or drop? A three-person MVP might skip performance tests and a dedicated QA pass; a healthcare app absolutely won’t. The point isn’t to do everything. It’s to decide, deliberately, what protects your users and what’s just ceremony. Visualising the whole flow is how you make sure every real risk has a checkpoint standing in front of it.

‍

Now let’s walk through it, stage by stage.

‍

Phase 1 - Before the commit

This is where the cheapest bugs die. Everything here runs on your machine, before a single line is shared.

  • Feature-flag anything risky. Wrap new or unfinished work behind a flag so it can ship dark and be switched on when it’s ready, not when the release train happens to leave.
  • Test on a simulator and a real device. Simulators are convenient; real hardware is honest. Some bugs only surface on a physical phone.
  • Mind the mobile-specific traps. Pay special attention to keyboard behaviour and small screens. That alone removes a huge share of real-device bugs. On React Native especially, also test older OS/API versions and a range of screen sizes.
  • Let Husky guard the commit. Pre-commit hooks (linters, formatters, TSC for TypeScript, and Knip) keep basic hygiene automatic and make sure what you commit actually compiles.
  • Unit tests here; E2E emphatically not. Unit tests are welcome at this stage. End-to-end tests are not. They’re slow, and forcing them into every commit kills the fast, self-contained rhythm that makes local work pleasant.

‍

Phase 2 - On push

Once code leaves your machine for the shared repo, automation takes over. The job here is simple: keep the repository in a working state, even if someone got sloppy with their local steps.

  • Run static analysis and unit tests automatically on push.
  • Keep the shared branch green, so nobody inherits a broken tree.

‍

One honest caveat: pipelines cost money and time. If CI minutes or budget are tight on a given project, it’s perfectly reasonable to make this step optional and move these checks entirely to the pull request. That’s a trade-off, not a failure. Just make it on purpose.

‍

Phase 3 - On the pull request

The PR is the last line of defence before code reaches the main branch, and it’s where most of the real scrutiny lives.

  • Run the pipeline. If you deferred checks on push, they run here: static analysis and unit tests, no exceptions.
  • Human code review. One or two reviewers read every change before it merges. Non-negotiable, as above.
  • E2E tests for regressions (if you have them). Run them here, against your testing environment (typically UAT or staging), in a standard/relaxed build to keep them quick. Their job is to catch the things you broke without noticing.
  • Performance tests. Where performance matters, measure it before the merge, not after a user complains.
  • Optional: a build for manual acceptance. For higher-assurance projects, generate a build straight from the PR so someone can tap through the actual feature before the merge is accepted.

‍

Phase 4 - On merge to main

Approved and merged is not the same as ready to ship. A couple of things need to happen as branches come together.

  • Validate the integrated main branch, not just the branches that fed it. Every pull request was tested in isolation. However, once multiple PRs land on main with overlapping features, fixes, and refactors, we end up with a combination nobody has actually run. So after a batch of merges, build and exercise main itself, and do it from a clean slate, wiping build folders and reinstalling dependencies, so you’re testing the tree as CI and a fresh clone will see it, not a local cache that happens to still hold a deleted file.

‍

Phase 5 - On the release candidate

Now there’s an actual candidate to poke at. Cut a release branch from main that pins the exact set of features and fixes going out, then put it through QA. The question at this stage is no longer whether a change works, but whether this specific composition of changes works.

  • Automated UI tests across devices. Run your UI flows on a spread of real devices without maintaining your own device lab. A hosted device cloud does this for you. We use Maestro Cloud; the principle matters more than the vendor.
  • Distribute the bundle to testers. Push the build to a testing agent (Firebase App Distribution, for example) so QA and stakeholders test the real artefact, not a local build.
  • Smoke tests. The basics, automated or manual: can you log in, and can you move through the core screens without anything falling over?
  • Manual testing of the new features. A human validates what actually changed in this release.
  • Check your environment. Verify that environment variables and secrets (GitHub Secrets, S3, etc) are current across staging and production. Out-of-date secrets are a classic, silent cause of build and runtime failures.

‍

Phase 6 - On release

The last stretch. QA has signed off; now you ship deliberately.

  • Tag the release once it’s stabilised, so the exact shipped version is always recoverable.
  • Smoke-test production. Before you distribute publicly, run the same basic checks against the shipped build (log in, move through the core screens, etc.) while it's still held back on a manual release or an internal track. Catching it here can cost a resubmission; catching it later will cost a hotfix.
  • Roll out in phases. Don’t hand the new version to 100% of users at once. A phased rollout means that if something did slip through, it reaches a small percentage first and you can halt it before it becomes, well, a full day of downtime.

‍

Other notes

Every checkpoint so far happens at a particular moment: a commit, a pull request, a merge, a release. The three below don't. They cut across all six phases, and each one is far easier to put in place at project kickoff than in the middle of a deployment incident.

‍

  • Automate version bumping. Bump the build/version code automatically wherever a shippable artefact can originate: on merge to main, and equally on release and hotfix branches, which cut builds without passing through main first. It’s a tiny piece of automation that saves you from the very avoidable rejection from Google Play or the App Store for shipping an outdated version code. The release you least want to see rejected is the emergency one.
  • Build a force-update path before you need one. Unlike the web, where everyone lands on the version you just deployed, a mobile user base is spread across every version you have ever shipped. When a crash turns out to be specific to one build, you have no way to pull those users forward and no guarantee they will ever update on their own. A force-update mechanism gives you that lever: the app checks its own version against a minimum you control remotely, then prompts or blocks when it falls behind.
  • Put agents in the loop. A lot of what this pipeline asks for is exactly the work AI has become good at: a first pass on code review, filling in unit and instrumentation tests, reading through logs and crash reports for the pattern that would cost a human an hour to spot. We have been folding these into our own delivery flow, but that is a whole article on its own, so we will get into what works and what doesn't in a follow-up post.

‍

A quick reference

The whole pipeline on one screen. Adapt the rows to your project’s risk level.

Stage What runs here Why it matters
1 · Before the commit Device testing, Mobile-specific checks, pre-commit hooks, Unit tests Kill the cheapest bugs before they’re even shared
2 · On push Static analysis, Unit tests Keep the shared repo always working
3 · On the pull request Code review, E2E tests, Performance tests, Optional manual pass The main gate before code hits main
4 · On merge to main Clean build and validation of integrated main, Automated version bump Catch conflicts; dodge store rejections
5 · On the release candidate Automated UI tests, Tester distribution, Smoke/Manual QA, Env checks Validate the real release candidate
6 · On release Tag, Production smoke test, Phased rollout Ship deliberately and limit the blast radius

‍

Final thoughts

None of this is exotic. Every checkpoint here is something most teams already know about. The difference between a calm release and a day of downtime usually isn’t knowledge, it’s discipline: doing the boring step even in the evening you’re certain you don’t need to.

‍

That’s really the whole point of a pipeline. It isn’t there for the releases that go well. It’s there for the one where you’re tired, or rushing, or a little too confident, so the process catches what you didn’t.

Adapt it to your project, cut what you don’t need, and keep the three non-negotiables no matter what. Your users will never notice the bugs you caught, which is exactly the point.

‍

If you’re building a mobile product and want a team that treats shipping with this level of care, we’d love to talk. We're on LinkedIn and Instagram, or you can send us a message. Thanks for reading.

Don't forget to share on your socials
Compose Multiplatform and SwiftUI code bridged in one mobile project

Bridging Compose Multiplatform with SwiftUI

by
Lucas Prioste
·
June 12, 2025
Engineering
Bridging Compose Multiplatform with SwiftUI
Hero animation transition between Flutter app screens

Making Flutter Mobile Apps Shine with 3 Easy UI Effects — Part III

by
Bruno Correia
·
April 10, 2025
Engineering
Making Flutter Mobile Apps Shine with 3 Easy UI Effects — Part III
The Pink Room Way — collaborative product discovery, cover art

The Pink Room Way, Part 1: The Collaborative Journey of Discovery

by
Mário Gago
·
July 19, 2024
Pink Room Way
The Pink Room Way, Part 1: The Collaborative Journey of Discovery
Animated shader shine effect on a Flutter app card (ShaderBuilder demo)

Making Flutter Mobile Apps Shine with 3 Easy UI Effects — Part I

by
Bruno Correia
·
February 27, 2025
Engineering
Making Flutter Mobile Apps Shine with 3 Easy UI Effects — Part I