Moving to Flutter After Writing Native in Three Languages
I stopped writing the same product twice. Here is what a single codebase gave me, what I gave up in return, and how native experience pays off while writing Flutter.
Every app I shipped between 2023 and 2025 involved the same routine: I wrote the same product twice. The iOS side in Swift, the Android side first in Java and later in Kotlin. In 2026 I moved to Flutter and worked with a single codebase for the first time. This post is my attempt to describe, as honestly as I can, what that move gave me and what I gave up in return.
Here is the short answer up front: what I gained was time, what I lost was direct contact. The interesting part is where that trade paid off and where it hurt.
The real cost of writing it twice is not the line count
The price of writing the same app in two languages usually gets summarized as "twice the code." In my experience that was not the real cost. Past a certain point code turns into muscle memory; building a screen in Swift and then building it again in Kotlin does not mean doing the thinking twice. The design decision has already been made, and the second pass feels more like translation.
The real cost is keeping the two apps aligned. In Cepte Perakende I defined in two separate places how installment options and return conditions are calculated, and at which step the disclosure texts required under consumer protection law no. 6502 appear. In ArenaX the contest eligibility checks lived in both codebases. In Social Connect the flow for eight different matchmaking platforms existed separately on each side.
The trap this structure creates is this: you notice a bug on the Android side and fix it, then weeks later you remember the same bug is still sitting there on iOS. Or the other way around. Two codebases quietly drift apart over time and nothing warns you about it. The one thing that makes you notice the drift is a user writing in to say "it didn't work like this on Android."
As the product grows, the question "which platform's behavior was the correct one" starts coming up. That is why I made a habit of writing the business rules on both sides with the same names and in the same order; at the very least I could put the two files side by side and compare them by eye.
The same screen, two different "right answers"
There was something I genuinely liked about writing native: the two platforms do not want the same screen in the same way. On Android the back gesture is part of the system; on iOS the edge swipe works differently. Permission flows differ; a permission that is asked once and then done with on one platform may, on the other, require sending the user to settings after a denial. Even how the screen behaves when the keyboard opens is not the same.
When you write them separately you cannot ignore these differences, because you are already working with the platform's own components. The app feels like it belongs there. That was the first thing I lost when I moved to a single codebase: the platform no longer makes those decisions for me, I make all of them.
The first thing I noticed in Flutter: the UI is entirely mine
Flutter builds the screen with its own rendering layer rather than the platform's components. The practical result: you build the layout once and it comes out the same on both platforms. The alignment grind that goes "the text looks a bit large on iOS," "the card edge is clipped differently on Android" disappears.
The price is that nothing resembles the platform by default. On Vakti Geçmeden and Hadi Yapalım a considerable share of my time went into tuning transitions, touch feedback and scroll behavior so they felt natural on both platforms. What comes for free in native, you build by hand here.
What I gained
| Task | Native (two codebases) | Flutter (single codebase) |
|---|---|---|
| Adding a new screen | Build twice, test twice | Once |
| Fixing a bug | Find it in two places, fix it in two places | In one place |
| Design consistency | Aligned by hand, drifts over time | Identical by nature |
| Platform-specific behavior | Direct, full control | Through a plugin or a platform channel |
| Store release | Two separate processes | Still two separate processes |
The last row of the table matters: the store side does not change at all. Two separate developer accounts, two separate review processes, two separate sets of release notes, two separate signing setups. Flutter shortens the time spent writing code; it does not shorten the time it takes to ship. If you went into the move expecting otherwise, that is exactly where the disappointment lands.
What I lost: depth and plugin dependency
When writing native, if I needed a platform capability there was only one place to go: the platform's own API. In Flutter a layer sits in between. For whatever you need, you either find an existing plugin or write one yourself.
Finding a plugin is the easy part. The hard part is these questions: who maintains it, how long after a new OS version ships before it is updated, does it really behave the same on both platforms? In native I could use an API the day the operating system shipped. Now someone has to wrap that API in between, and if that someone is not me, I do not set the schedule either.
Before pulling a plugin into a project I check two things: the date of its last update and how readable the native code inside it is. The second may sound excessive, but when something breaks, you are the one who will be doing the debugging.
When you need a capability that has no plugin, you write a platform channel. The Dart side looks roughly like this:
static const _kanal = MethodChannel('uygulama/cihaz');
Future<String?> cihazEtiketi() async {
try {
return await _kanal.invokeMethod<String>('cihazEtiketi');
} on PlatformException {
// must be handled separately on each platform
return null;
}
}
So what sits at the other end of the channel? Kotlin and Swift. In other words you do not stop writing native, you just write less of it. To me this is the most misrepresented part of the move. Flutter does not make native knowledge unnecessary; it makes it rarer but more critical.
Why knowing native is an advantage when writing Flutter
Someone starting Flutter from scratch and someone starting after writing native in three languages are not standing in the same place. The difference is invisible while everything goes well; it shows up when something breaks.
- Build errors usually surface on the Gradle or Xcode side rather than the Dart side. If you have read that output before, it is not frightening, it is just an ordinary day.
- Permission strings, background execution limits, notification channels, target API level — all of these are still native concepts. Flutter does not remove them, it puts an interface on top of them.
- When a plugin does not behave the way you expected, you can open up the Kotlin or Swift code inside it and read it. That alone changes how long debugging takes.
- When a store rejection arrives, you know where the cause is. A rejection over a permission description is an iOS matter, not a Flutter one.
- Signing, version numbering, test tracks — I already knew these, and they never slowed me down during the move.
On the backend side, nothing changed. I use Firebase in both my native and my Flutter projects: Firestore, Storage, Cloud Functions, Messaging. Knowing how to set up the data model and how to write the security rules turned out to be knowledge that does not disappear when you switch languages. That was the most comfortable part of the move; the problems always showed up on the client side.
Where I still drop down to native
Social Connect had video calling, and for that I hosted Jitsi Meet on my own server on DigitalOcean. In work like this — real-time media, deep contact with the camera and microphone, continuous background execution — it is hard to avoid going down to the native side. Even if I used Flutter, I know part of that job would still be written in Kotlin and Swift.
Native is also still reasonable for a product targeting a single platform. For an app that will only ship on Android, writing Kotlin is more direct and holds fewer surprises than putting a layer in between.
For products built around screens, forms, lists and data, a single codebase is comfortable by a wide margin. For products that lean on hardware and sit close to the system, you pay the time you saved back in platform channels.
Where I stand right now
In 2026 I wrote three apps with Flutter: Vakti Geçmeden, Hadi Yapalım and Sinepia, which is in store review. All three: one codebase, two stores. If I had written the same work natively I could not have produced this much in the same time; I can say that comfortably.
Even so, I do not want to present Flutter as "the right answer." The years I spent on native were not wasted; quite the opposite, those years are the reason I am comfortable in Flutter. If you know what is underneath the layer, the layer makes you faster. If you do not, that same layer turns into a wall at the first serious problem.
These days the question I ask when starting new work is not "which technology is better." It is this: how much business does this product have with the platform? If the answer is "not much," a single codebase; if it is "a lot," native or a hybrid path. Having written native in three languages lets me answer that question from experience rather than guesswork.
- Flutter
- Swift
- Kotlin
- Java
- Cross Platform
- Mobile
Demir Taşdemir
Mobile App & Web Developer
I have been building software since 2018. I have published 11 apps on the App Store and Google Play; right now I am working on 6 mobile apps, 1 e-commerce platform and 1 desktop game.
Choosing the tool to fit the product
If you want to talk through your project with someone who has seen both the native and the Flutter side, just write to me.