
Last updated: October 30, 2026
Quire built its Android app with Google's Flutter after writing the iOS app in Swift, and this is the engineering write-up. How Flutter draws on canvas, how its bridge works, and how Dart and Flutter compare with Swift and the iOS SDK at the language, framework, and performance level.
Every team with a phone app ends up in the same meeting. The iOS app has a feature the Android app doesn't. The Android app has a bug the iOS app fixed in March. Two codebases, two release trains, and one small team that was supposed to be building the product.
We wrote our iOS app in Swift a couple of years ago. Last year we chose Flutter for the Android app. This is what that was like, the good and the bad, from the front-end developer who did most of it.
I'm a front-end developer at Quire, a project management tool for capturing ideas, managing tasks, and working with a team.
We've used Dart on our server and our web client for years, so when Google released Flutter, a mobile framework that runs on Dart, we were already most of the way to a decision. Flutter shipped its first alpha in May 2017 and was still in beta while we built on it. We shipped anyway.
Two months after the official launch in the Play Store, I think we've learned enough to be useful to anyone in the Flutter community or thinking of joining it.
Read more on how Quire built its large application with Dart.
One language, one platform.
We ship an update almost every week, sometimes every day, and maintaining two copies of the same logic had become the slowest part of the week.
We tried the hybrid route first. A Cordova build gave us one codebase, and a UI that didn't reach the standard we wanted. Scrolling stuttered. Transitions felt a step removed from native.
Flutter gave us three things at once. UI rendering fast enough that the stutter went away, smooth transitions, and a core language we already shared across server, web, and now mobile. One codebase, one model, a fast development cycle. That was the whole requirement.
Flutter is Google's UI framework for Android and iOS. You write the UI once in Dart and run it on both platforms. When you need something only the platform can do, a bridge carries your Dart call to native code and back, so almost anything Swift, Kotlin, or Java can do is still reachable.
Two ideas carry the whole framework.
Drawing and bridging.
Flutter draws the UI itself, directly onto a canvas inside iOS's UIViewController or Android's Activity. Your Dart UI code compiles to machine code and paints. There's no translation into platform widgets, which is why Flutter's UI and transitions feel smooth. Compared with CSS, it uses a simpler layout model, and simpler is faster.
When the app needs a platform resource, the bridge carries the Dart command and its data to the native layer and brings the answer back. Because the drawing already happens inside Flutter, the bridge isn't involved in rendering at all. That cuts the context-switch cost well below what hybrid frameworks like React Native pay on every frame.
Flutter is open source. When an API call does something surprising, you read the framework and find out why. You can trace the whole system's architecture from the top.
With the iOS SDK, the equivalent is a shelf of books, a lot of trial and error, and the slow discovery of rules nobody wrote down. I've done both.
I prefer reading the source.
Swift people talk about local reasoning, the ability to understand a piece of code by reading only that piece. The language supports it. The SDK often doesn't.
Set a delegate on a UIView component, or a callback on a function call, and the related logic ends up in different sections of the file because that's how the delegate pattern works. Callbacks have their own problem, which is callback hell.
What's the best async pattern in Swift? Callbacks, GCD, PromiseKit, Reactive Swift? Everyone has a different answer, which means nobody has one. In Dart, the same job reads like this.
Future scrollToComment(Comment comment) async {
await model.scrollTo(comment.position);
await model.highlight(comment);
}
That's local reasoning you can see.
Swift loses a second point on build time. Three years ago we started porting our model and building the iOS UI in Swift. The codebase grew, and the compile time grew with it. Two or three minutes for a release build is tolerable, since it's automated and rare. A one-line style change that takes over a minute to check is not, and in our worst cases it did.
There are articles on cutting Swift compile time by changing how you write Swift. That's backwards. We picked a new language for clearer syntax, not for syntax that compiles faster.
Dart has an ahead-of-time compiler for production speed and a JIT compiler for development. A one-line change now shows up in about half a second. We're pleased with that number.
Storyboards in Interface Builder look like the dream. Drag a widget, drop a widget, done. In practice the first advice you get is to keep the storyboard small, and the second is to stop using it.
We stopped when we hit its limits and its performance cost. Building iOS UI in code is its own kind of pain. Components, Auto Layout, legacy layout, and transitions all take more code than you expect, the code blocks sprawl, and every rule lives in a document you haven't read yet.
Flutter treats code as the UI template. In Dart 2.0 you compose a screen the way you'd write JSX or HTML, and the result stays readable months later.
Container(
child: Center(
child: Row(
children: [
Icon('smile'),
Text('Hi', style: TextStyle(color: Colors.red))
]
)
)
)
iOS's official layout solution has never made sense to me, and the third-party ones keep winning for a reason. Flutter uses a Flexbox-style layout model, which I'd pick over any Auto Layout look-alike. There's also a guide that walks web developers through each step. After many years as a web front-end developer, that was good news.
The bridge. If there's no Flutter way to do something, we can at least do it the way a native iOS or Android developer would. That promise let us commit to a beta framework (alpha, when we started the first Play Store build) and still know we could ship any feature.
We used it.
Compressing a photo in Dart took about 50 seconds at 70 percent quality. In Java, half a second.
Flutter is in beta and moving fast, so anything below may have changed by the time you read it. These are the gaps we hit and worked around.
A ListView scrolls only by offset. That's fine when every row is the same height and awkward when rows vary. Our workaround in the app has three steps.
For very long lists we added a recommended row height. Scrolling by that estimate keeps the jumps accurate and the motion smooth.
Design the model so it can be listened to many times and knows how many listeners it has. Bind in initState, unbind in dispose, and start or stop the model's work when a binding exists with no listener.
Don't emit events while binding. Firing an event inside initState reaches other listeners, which update their state while the context scope is still locked.
Building is cheaper than it looks. If you cache a widget, you also own its cleanup. Leave it to Flutter, which borrows React's approach, diffs the tree, and updates only what changed.
Our tree view runs past 2,000 rows in a ListView (we didn't use the builder variant, for reasons of our own), and it's faster than we expected. A stateful widget only creates row state for rows inside the viewport. For the build cache, Flutter published an article worth reading.
Flutter's focus is Material components. They work on iOS, since Material was designed to cross platforms, but some don't feel right there. The wheel-style date picker is the obvious example. iOS users notice when the platform's behavior is missing.
Input has rough edges. In issue #13765, you can't see the newline until you've typed more words. In issue #5986, there's no way to know which key the user pressed.
Yes.
We enjoyed building the Android app in Flutter, and we expect to enjoy it more once it leaves beta. Through alpha and beta it has been stable, and the Flutter team answers questions and issues quickly.
Being open source, it lets you see what's been fixed and what's changing. You can fix an issue yourself and send it back.
Flutter is a fast cross-platform framework with native behavior, and for a small team shipping on two platforms, that combination is hard to beat. We're planning to replace the Swift-based iOS app with Flutter in the coming months, and I'll write up that experience when it's done.
We did keep building with it. See how Flutter also powers Quire's mobile Timeline.
Related: Quire Mobile 3.0: Kanban Board comes to Mobile App, a later milestone in the same Flutter-built mobile app.
Every mobile team faces the same fork. Here's how the options stacked up when we chose ours.
| Approach | The trade-off | Why it mattered for us |
|---|---|---|
| Fully native (two codebases) | Best performance, but you build and maintain everything twice | Too slow for a small team shipping on two platforms at once |
| Traditional cross-platform | One codebase, but the UI often feels a step removed from native | We didn't want an app that felt second-class on either phone |
| Flutter | One codebase that draws its own pixels, close to native feel | Let us move fast and still ship an app that feels right on iOS and Android |
Quire wanted one language and platform instead of maintaining two codebases. Flutter delivers high-performance UI, smooth transitions, and a single Dart codebase Quire already shared across server and web.
Flutter is Google's UI framework for building Android and iOS apps from one codebase. It bridges to native code, so it can do almost anything Swift, Kotlin, or Java can.
It's written in Dart, the same language Quire already used on its server and web. Dart has an AOT compiler for fast releases and a JIT compiler for fast development.
At the time, iOS was built in Swift while Android used Flutter. Quire planned to replace the Swift-based iOS app with Flutter soon after.