SR
S. Romero · Engineering April 28, 2026 · 10 min read
One codebase, four platforms, one shipped 1.0. The wins, the surprises, the things we'd skip next time.
When we picked Flutter for AGirl we were betting that one codebase could ship Mac + iOS + Android + Web without too much pain. Eight months in, we shipped 1.0 on all four. Here’s the unvarnished version of how it went.
What worked
- Voice is voice. Once you hide the platform layer behind a thin Dart channel, the same chat surface runs everywhere. Voice in/out, waveform rendering, push-to-talk — none of it had to be re-implemented per platform.
- Dart’s mood matches the UI. Strict typing, hot reload, immutable models. We get the productivity of a scripting language with the safety of a typed one. After a year I don’t miss TypeScript.
- One design system, four surfaces. Our design tokens drop into Dart unchanged. Our Pencil designs render in Flutter with a single shared theme file. That alone saved us a quarter.
What surprised us
- macOS menubar wasn’t free. Flutter doesn’t ship a menubar primitive, and the community options were rough. We ended up writing a native Swift host that talks to Flutter via method channels. Worth the cost but the cost was real.
- Web target is a different product. Flutter Web works, but it ships as a giant canvas-rendered runtime. SEO is impossible. Sharing links is awkward. We made peace with the split: marketing on Astro, product on Flutter Web at app.agirl.app.
- Audio is platform-specific anyway. We thought a unified audio API would carry us. It didn’t. We have macOS-specific code for permissions, iOS-specific code for AVAudioSession, Android-specific code for AudioFocus, and Web-specific code for AudioContext. The shared bit is the model, not the wire.
What we’d skip
- Wrapping every platform thing. Some platform APIs are simple enough that the wrapper costs more than the duplication. The first version of our notifications layer had three abstraction levels. The current one is a switch statement.
- Custom paint for everything. We tried to render the waveform with custom paint everywhere. Works fine on desktop. Murders battery on phones. We now use platform-native waveform views on mobile.
What we’d do again
All of it. The bet paid. One team, one codebase, four shipped surfaces. The alternative was four teams and we don’t have four teams.