← Back to posts Cover image for Flutter vs. KMP: Choosing the Right Cross-Platform Framework for Your Existing Native App

Flutter vs. KMP: Choosing the Right Cross-Platform Framework for Your Existing Native App

· 5 min read
Weekly Digest

The Flutter news you actually need

No spam, ever. Unsubscribe in one click.

Chris
By Chris

You have a native app. It works. Your team knows the platform-specific intricacies. Now, the business wants to expand reach or improve development efficiency by going cross-platform. The two leading contenders for a team in your position are Flutter and Kotlin Multiplatform (KMP). This isn’t a greenfield project, so the choice has profound implications for your existing code, team skills, and long-term maintenance. Let’s break it down.

The Core Philosophy: Rewrite vs. Reuse

The fundamental difference lies in the approach to your existing investment.

  • Flutter is a complete UI toolkit. You build the entire interface (and most logic) in Dart, rendering to a canvas. It’s a strategic rewrite of the presentation and business layers for a consistent, high-performance experience across platforms.
  • Kotlin Multiplatform is a business logic sharing technology. You write your core logic (networking, models, business rules) in Kotlin once and share it across iOS, Android, and others. The UI remains native (Swift/UIKit for iOS, Kotlin/Jetpack Compose for Android). It’s a tactical reuse of your core app.

Evaluating Your Team and Codebase

Your team has an iOS dev (Obj-C) and an Android dev (XML-based UI). This setup is crucial.

For Flutter:

  • Pros: You both learn a new, singular technology (Dart/Flutter) from a similar starting point. It creates a unified team. The developer experience with hot reload, a single codebase, and a rich widget library is excellent. UI inconsistencies vanish.
  • Cons: It’s a full rewrite. Your 7-year iOS veteran’s deep platform knowledge becomes less critical in the short term. The Android dev’s XML/View expertise doesn’t directly translate. You must also ensure Flutter’s plugins support your Bluetooth/WiFi device connectivity needs.

For KMP:

  • Pros: Your Android dev can leverage Kotlin skills immediately. You incrementally extract and share business logic, preserving your native UI investments. It’s less disruptive initially.
  • Cons: Your iOS dev, working in Objective-C, faces a steeper integration curve with Kotlin-generated frameworks. You now maintain three codebases: shared Kotlin, native Android UI, and native iOS UI. The “write once, run anywhere” promise is for logic, not UI.

The Bluetooth/WiFi Connectivity Test

This is a key practical hurdle. Let’s see how each framework handles a common Bluetooth operation.

In Flutter, you rely on community or official plugins. You write platform-agnostic Dart code.

import 'package:flutter_blue_plus/flutter_blue_plus.dart';

class DeviceScanner {
  final FlutterBluePlus _flutterBlue = FlutterBluePlus.instance;

  Future<List<BluetoothDevice>> scanForDevices() async {
    // Start scanning
    _flutterBlue.startScan(timeout: Duration(seconds: 10));

    // Listen to results
    return await _flutterBlue.scanResults
        .firstWhere((results) => results.isNotEmpty)
        .then((results) => results.map((r) => r.device).toList());
  }

  Future<void> connectToDevice(BluetoothDevice device) async {
    await device.connect();
    print('Connected to ${device.name}');
  }
}

You must vet the plugin (flutter_blue_plus in this case) for stability, API coverage, and maintenance.

In KMP, you write the core scanning/connection logic in Kotlin, but the platform-specific implementation (using CoreBluetooth on iOS, Android Bluetooth APIs) lives in expect/actual declarations.

// In shared KMP module (CommonMain)
expect class PlatformBluetoothScanner {
    fun startScan(deviceDiscovered: (DiscoveredDevice) -> Unit)
    fun stopScan()
}

data class DiscoveredDevice(val name: String, val identifier: String)

// Android-specific implementation (AndroidMain)
actual class PlatformBluetoothScanner actual constructor() {
    private val bluetoothAdapter: BluetoothAdapter? = ...
    actual fun startScan(deviceDiscovered: (DiscoveredDevice) -> Unit) {
        // Implement using Android Bluetooth APIs
    }
    actual fun stopScan() { /* Android impl */ }
}

// iOS-specific implementation (iOSMain)
actual class PlatformBluetoothScanner actual constructor() {
    private val centralManager: CBCentralManager = ...
    actual fun startScan(deviceDiscovered: (DiscoveredDevice) -> Unit) {
        // Implement using CoreBluetooth
    }
    actual fun stopScan() { /* iOS impl */ }
}

KMP gives you fine-grained control but requires you to write and maintain the platform-specific bridges yourself.

Common Pitfalls to Avoid

  • Underestimating Platform Channels (Flutter): Assuming Flutter plugins exist for every obscure native SDK. For complex native integrations, you may need to write custom platform channels, which requires native knowledge.
  • Over-Promising Shared Code (KMP): Expecting 80%+ code sharing from day one. Start small (models, repositories). Sharing UI state logic is possible with libraries like compose-multiplatform, but that’s a larger shift.
  • Ignoring Team Morale: Forcing a senior iOS dev into a pure Kotlin logic role or an Android dev into a full Dart rewrite without buy-in leads to friction. Let each team member prototype their preferred option.

A Practical Decision Framework

Ask your team these questions:

  1. What’s the primary goal? Uniform UI/UX and fastest feature parity → lean Flutter. Code reuse and preserving native feel → lean KMP.
  2. Can we afford a rewrite? If the app is large and complex, a full Flutter rewrite is a major project. KMP allows a gradual migration.
  3. Where is our complexity? If it’s mostly in complex, custom UI, Flutter’s widget system is a powerhouse. If it’s in intricate business logic and device communication, KMP’s logic sharing shines.
  4. What does our team want to learn? A unified Dart future, or a split path of Kotlin logic + native UI?

The Verdict

There is no universally “right” answer. For your specific scenario—with an Objective-C codebase and a need for deep Bluetooth/WiFi integration—the path of least initial resistance might be Kotlin Multiplatform. It allows your Android dev to drive the shared module while your iOS dev modernizes the UI layer to Swift/SwiftUI, integrating the shared Kotlin code.

However, if your long-term vision is a unified team, a single codebase, and freedom from native platform UI cycles, Flutter is the more transformative and ultimately simpler bet. The investment in learning Dart and rewriting the UI pays dividends in velocity and consistency.

Actionable Next Step: Don’t debate in the abstract. Spend one week on a spike. Have your Android dev build a simple screen (with a mock Bluetooth scan) in Flutter. Have your iOS dev integrate a small, shared KMP module (like a date formatter) into the existing app. The hands-on experience will reveal more than any blog post. The right framework is the one your team can successfully execute and maintain for the next five years.

This blog is produced with the assistance of AI by a human editor. Learn more

Related Posts

Cover image for Mastering Internationalization in Flutter: Centralized Strings for Scalable Apps

Mastering Internationalization in Flutter: Centralized Strings for Scalable Apps

As Flutter applications grow, managing strings for multiple languages or just keeping text consistent becomes a challenge. This post will guide developers through effective strategies for centralizing strings, implementing robust internationalization (i18n) and localization (l10n), and leveraging tools to streamline the process for small to large-scale projects.

Cover image for Flutter Performance Deep Dive: Optimizing 'Vibe Coded' Apps for Speed and Responsiveness

Flutter Performance Deep Dive: Optimizing 'Vibe Coded' Apps for Speed and Responsiveness

Many developers start with 'vibe coding' for rapid prototyping, but this often leads to slow, unresponsive Flutter apps. This post will guide you through identifying performance bottlenecks in your Flutter projects, covering common culprits like unnecessary widget rebuilds, inefficient state management, and debugging differences between debug and release modes, to help you transform a 'vibe coded' app into a smooth, production-ready experience.

Cover image for Flutter & AI Code Generation: Beyond 'Vibe Coding' for Solo Developers

Flutter & AI Code Generation: Beyond 'Vibe Coding' for Solo Developers

AI code generation tools are rapidly evolving, but how can Flutter developers, especially solo founders, leverage them effectively without falling into 'vibe coding' pitfalls? This post will explore strategies for using AI to boost productivity, maintain code quality, and ensure architectural consistency in Flutter projects, addressing common concerns like context drift and code reuse.