Cross-platform Development: Flutter, React Native, KMM
A startup wants two applications — iOS and Android — with a budget for one team. Or a corporation wants to launch an internal tool in 3 months for both platforms. Cross-platform development solves a specific economic problem: one codebase versus two. The question isn't "cross-platform or native" — it's "which tool for which task."
Three main players now: Flutter, React Native, and Kotlin Multiplatform Mobile. They solve different problems and don't compare well head-to-head.
Flutter vs React Native: detailed breakdown
This is the comparison that actually influences the choice. Let's break it down by specific technical characteristics.
Rendering Model
Flutter renders UI independently through Impeller (replaced Skia from Flutter 3.10). The platform provides only a canvas — Flutter draws every pixel itself. This means:
- Pixel-perfect precision on all platforms. The same widget looks identical on iOS and Android — good for branded applications, bad if you need to look "native" on each platform.
- No OS version dependency. Material 3 in Flutter works the same on Android 8 and Android 14. Android system components don't participate.
-
Platform channels for native code. Access to camera, Bluetooth, NFC — via
MethodChannelorEventChannel.flutter_camera,flutter_blue_plus— these are wrappers over platform channels.
React Native uses native platform components. <View> on iOS is UIView. <Text> is UILabel. This means:
- Native look and feel without extra effort.
- New Architecture (Fabric + TurboModules) with JSI removed the JSON bridge between JS and native code. Synchronous calls work without serialization. This is critical for animations and gestures.
- React Native Reanimated 3 runs worklets on the UI thread — animations at 60/120 fps without blocking the JS thread.
Performance in Practice
For most business applications, the performance difference between Flutter and React Native New Architecture is imperceptible to users. The difference appears in edge cases.
Flutter is slower when interacting with platform APIs through platform channels — each call is asynchronous with data serialization overhead. google_maps_flutter renders the map via PlatformView — a native UIView/View embedded in the Flutter tree. Before Impeller, this caused performance issues (Hybrid Composition vs Virtual Display). With Impeller, the situation improved, but PlatformView is still more expensive than a pure Flutter widget.
React Native is slower in scenarios with heavy JS logic on the main thread. Parsing large JSON, complex calculations — this blocks the JS thread and causes visible UI freezes. Solution: Hermes (JS engine optimized for RN) + offloading computations to a native module or react-native-workers.
Ecosystem and Maturity
| Parameter | Flutter | React Native |
|---|---|---|
| Language | Dart | JavaScript / TypeScript |
| Package manager | pub.dev | npm / yarn |
| Major companies | Google, Alibaba, BMW | Meta, Microsoft, Shopify |
| Hot reload | Yes (stateful) | Yes (Fast Refresh) |
| Desktop (macOS, Windows) | Yes (stable) | Experimental |
| Web | Yes (CanvasKit / HTML) | Partial (via React) |
| APK/IPA size | ~6 MB baseline | ~4 MB baseline |
Dart is a barrier to entry for teams with JS/TS background. Learning basic Dart takes a week. Rebuilding your thinking around Flutter widgets and the widget tree takes longer.
TypeScript in React Native is the de facto standard. Teams with React experience become productive faster.
When to Choose Flutter
- Need unified branded UI across all platforms (iOS, Android, Web, Desktop)
- Team is ready for Dart
- Lots of custom animation and UI — Flutter is more predictable
- Application isn't tied to specific native APIs
When to Choose React Native
- Team has React/TypeScript expertise
- Need native platform look and feel
- Active use of native components (Maps, Camera with native capabilities)
- Code sharing with React web through a monorepo
Kotlin Multiplatform Mobile: a different story
KMM solves not the UI problem, but the problem of duplicating business logic. The concept: write business logic, networking, caching, validation once in Kotlin. iOS gets a .framework via Kotlin/Native, Android uses the library directly. UI on each platform is native.
// Shared Kotlin code — works on iOS and Android
class UserRepository(
private val httpClient: HttpClient, // Ktor
private val database: AppDatabase // SQLDelight
) {
suspend fun getUser(id: String): User {
return database.userQueries.selectById(id).executeAsOneOrNull()
?: httpClient.get("$BASE_URL/users/$id").body<User>().also {
database.userQueries.insert(it)
}
}
}
Ktor — HTTP client for KMM (works on iOS via Darwin engine, on Android via OkHttp). SQLDelight generates typesafe Kotlin API for SQLite, works on both platforms.
Real KMM Limitations
Coroutines on iOS. Kotlin Coroutines work, but with caveats. suspend functions from shared code are called from iOS via automatically generated wrappers. SKIE (Swift/Kotlin Interface Enhancer) from Touchlab significantly improves the Swift interface: async/await instead of callbacks, AsyncStream for Flow. Without SKIE, working with coroutines from Swift is uncomfortable.
Compose Multiplatform. JetBrains develops Compose for iOS — UI on Compose works on iOS via Metal. This blurs the line with Flutter: one Compose code for both platforms. Status in 2024: Beta, early adopters in production (Touchlab, JetBrains own products), but stability is lower than Flutter.
iOS integration complexity. XCFramework from KMM module adds to the Xcode project. SPM integration appeared and works. But iOS developers must understand Kotlin API and memory management rules via Kotlin/Native (ARC + Kotlin GC work together, not always obvious).
When KMM is Justified
A company already has mature iOS and Android teams duplicating business logic. Migrating everything to Flutter or React Native is too radical. KMM lets you start small: extract the network layer and models to shared, keep UI native. Gradual migration without rewriting everything.
Typical Mistakes When Choosing Technology
Choosing Flutter "because of unified codebase" for an application heavily tied to native APIs (custom camera, BLE, background processing). Implementing this via platform channels is additional complexity that eats the speed-to-market advantage.
React Native without understanding JS thread. Heavy operations on JS thread cause visible freezes. This is solvable, but requires architecture understanding — otherwise the application will perform worse than native.
KMM without an iOS developer on the team. Shared Kotlin code requires an iOS engineer to integrate the framework into Xcode, write SwiftUI on top of KMM API, and debug Kotlin/Native crashes.
Process and Timeline
A cross-platform project goes through the same stages as a native one: audit requirements → choose stack → design → develop → test on real devices of both platforms → publish to App Store and Google Play → support.
Testing on real devices is not optional. The emulator doesn't reproduce memory issues on budget Android phones and doesn't show gesture behavior differences on iOS.
| Project Type | Flutter | React Native |
|---|---|---|
| MVP (8–12 screens) | 7–12 weeks | 7–12 weeks |
| Medium (20–30 screens) | 3–5 months | 3–5 months |
| Complex (native integrations, AI) | 5–8 months | 5–8 months |
Cost is individual after analyzing the stack and requirements.







