Flutter or native iOS/Android: how to choose
Product experience, hardware, performance, team and maintenance: the technical criteria for choosing Flutter, Swift and Kotlin.
The choice between Flutter and native development often comes too early in a mobile project. An idea, a few screens, then a question framed as final: should the team share code or build two separate applications? Asked this way, it hides the constraints that actually matter.
The decision concerns an architecture and how it will be operated over time. It must connect the product experience, operating-system APIs, critical performance, available skills and the way the team will ship each release. Flutter and native can both be sound choices; neither compensates for a poorly understood scope.
What you are actually choosing
Flutter: a shared product layer
Flutter brings the interface and much of the application logic into a Dart project. Its rendering engine gives the team consistent control over visual components. Functions specific to iOS and Android go through packages or through Swift and Kotlin bridges written for the product.
Sharing this layer does not remove the platforms. Permissions, lifecycles, signing, builds, notifications and store releases still need to be handled for each one. Flutter primarily reduces duplication where the product behaves in the same way.
Swift and Kotlin: direct access to each platform
A native application uses the frameworks, components and tools supplied by Apple or Google. New system APIs are available without waiting for an intermediate package to expose them. Navigation, accessibility, background execution and hardware integration can be adjusted closely to each operating system.
Two projects do not necessarily mean two products designed in silos. API contracts, business rules, the design system, test scenarios and the measurement strategy can remain shared. Each implementation does, however, require explicit ownership and a clear release cycle.
The criteria that should decide
Should the experience be shared or specific to each OS?
If iOS and Android share the same journeys, feature cadence and a strong visual identity, a common Flutter layer is a natural option. If the product's value instead depends on platform-specific components, system extensions or genuinely different conventions, native development reduces the abstractions the team must maintain.
Make this decision screen by screen and journey by journey. A shared visual language does not require identical interactions, and a Flutter application can adapt selected behaviours to the platform. The important part is identifying those differences before they turn into a sequence of exceptions.
How large is the native integration surface?
Camera, Bluetooth, audio, payments, notifications, biometrics, widgets, sharing and background execution do not all have the same support across cross-platform libraries. For each capability, check the targeted OS versions, package maintenance, permissions, degraded modes and whether the team can fix the native code when required.
A specialised integration does not automatically rule out Flutter. It can become a native module behind a narrow, testable Dart interface. In Meduz, the Flutter application uses Swift and Kotlin plugins to communicate with BLE hardware. This boundary keeps the experience shared while placing the hardware constraint in each platform's language.
Where is the performance risk?
“Native is faster” and “Flutter performs the same” are equally unhelpful shortcuts. Locate the critical path: start-up, a dense list, animation, image processing, audio, video, encryption, memory use or battery use. Then prototype that path on the devices the product actually targets.
A slow network or backend does not disappear with Swift or Kotlin. Conversely, a compute-heavy workload or a system API that is difficult to abstract may justify a native implementation even if the rest of the application remains in Flutter. Measurement should come before the verdict.
Who will own the product after the first release?
Team composition matters as much as technology. A Flutter-focused team can evolve both platforms through one workflow. Two native teams can progress with strong autonomy provided their contracts and priorities stay aligned. An organisation that cannot review or maintain its native modules creates a risk even when those modules are small.
Recruitment, code review, support, store knowledge and transfer to a new team also matter. Repository count or lines of code alone do not describe the cost of ownership.
Whose release cadence are you accepting?
Flutter adds the framework and package lifecycle to those of iOS and Android. Native development depends directly on changes in each SDK. In either case, OS versions, third-party libraries and store rules continue to evolve.
For every critical dependency, document its owner, maintenance pattern, failure behaviour and the effort required to replace it. A convenient package with no exit strategy quickly becomes an implicit architecture decision.
Three possible architectures, not merely two
Flutter-first
Most of the interface and application logic is shared. Native access goes through selected packages or internal plugins. This approach fits when journeys remain close across platforms and the team owns the product's few native boundaries.
Vibly illustrates a mobile product whose feed, chat and ticketing features are carried by a Flutter application shared across iOS and Android. The mobile-layer decision remains separate from the API and infrastructure decisions.
Native-first
Each platform owns its application and directly adopts its components, lifecycle and APIs. This organisation is coherent when the experiences genuinely differ, system capabilities are central to the product or established Swift and Kotlin teams already own it.
Flutter with native islands
The product remains shared, but specialised paths are isolated in Swift and Kotlin modules: BLE, media, intensive processing or a proprietary SDK, for example. The interface between Dart and native code should be small, versioned and observable. It should carry product intent, not the internal details of each SDK.
A product takeover can also preserve boundaries that work and replace problematic ones progressively. The Flutter application behind Blisterr and its LL-HLS/WebRTC streaming architecture show why the mobile-client decision should not be confused with media and backend service choices.
Validate the choice with a vertical slice
A navigation prototype does not test the main risk. Build a thin slice through the real system instead: the most demanding screen, the API, authentication, one critical native integration, error paths and a distributable build.
- Test modest devices and the OS versions you will support.
- Cover start-up, backgrounding, resume, permissions and network interruption.
- Check the keyboard, screen reader, text scaling and navigation without precise gestures.
- Run signing and distribution pipelines for both platforms.
- Measure the critical path with the appropriate profiling tools.
- Have the future owner review the native bridge.
This slice is not meant to declare a universal winner. It reveals how much platform-specific code exists, where dependencies stop helping and whether the team can diagnose the complete product.
Evaluate ownership cost, not only the first build
Compare scoping, implementation, native bridges, per-platform QA, release pipelines, dependency updates and operational maintenance. Include code transfer and the skills required during an incident. A shared codebase can reduce some duplication without removing every one of these areas.
For an existing application, rewrite cost also matters. Before migrating, inventory stable journeys, system-coupled modules, test coverage, abandoned dependencies and available production data. A progressive migration is often easier to control than a complete replacement driven only by framework choice.
Mistakes that distort the decision
- Choosing Flutter only to put “one codebase” in a budget without inventorying platform-specific work.
- Choosing native on principle without identifying a constraint that benefits from direct platform access.
- Comparing simple demos instead of prototyping the product's riskiest journey.
- Depending on a critical package without checking its maintenance or planning how to replace it.
- Deferring accessibility, background and recovery tests until the end of the project.
- Forgetting the team that will maintain the application after it reaches production.
Decision checklist
- Should the iOS and Android journeys remain aligned?
- Which capabilities directly touch operating-system APIs?
- Which paths require a prototype or performance measurement?
- Are critical packages maintained and replaceable?
- Who can develop and diagnose the Swift and Kotlin components?
- How will both platforms be tested and released?
- What is the SDK and dependency update plan?
- Will the code join an existing team or be transferred?
- Can a migration be divided without blocking releases?
- Which assumptions must be revisited after initial use?
Choose a boundary, not a camp
The right answer is not always Flutter or native across the whole product. A clear boundary can reserve Swift and Kotlin for capabilities that need platform APIs while sharing the experience and logic that genuinely benefit from being common.
Our mobile development service covers scoping, architecture and delivery on iOS and Android. If your decision depends on a hardware constraint, an existing codebase or a journey that is difficult to evaluate, let's discuss it before choosing the stack.