Cross-platform Mobile — Comparing the Candidates
Cross-platform Mobile — Comparing the Candidates
The desire to build the same app for iOS, Android, and the web from a single codebase has been around for a long time. The tools that emerged as answers picked different approaches in different eras. Some sit on top of a WebView, some call native widgets through a JavaScript bridge, and some draw every pixel onto their own canvas. This post walks through the timeline from PhoneGap to Tauri Mobile and the trade-offs of each candidate.
1. Timeline
| Tool | First appeared | Model |
|---|---|---|
| PhoneGap | 2009 (Nitobi) | WebView + native plugins |
| Apache Cordova | 2011 (PhoneGap donated to ASF) | WebView + plugins |
| Ionic | 2013 | Cordova / Capacitor + Angular / React / Vue UI |
| React Native | 2015 (Meta) | JS bridge + native widgets |
| NativeScript | 2014 | JS + direct native access |
| Xamarin | 2011 (acquired by Microsoft 2016) | C# + Mono. EoL in 2024 |
| Flutter | 2017 (Google) | Dart + own canvas (Skia → Impeller) |
| Capacitor | 2019 (Ionic) | WebView successor with a cleaner plugin model |
| Expo | 2015 → workflow on top of React Native | Managed + EAS Build |
| .NET MAUI | 2022 (Xamarin successor) | C# + platform handlers |
| Kotlin Multiplatform | 2018 alpha · 2023 stable | Shared Kotlin code, UI via Compose Multiplatform or native |
| Tauri Mobile | 2024 alpha | WebView + Rust core. Mobile extension of desktop Tauri |
2. Three models
Sit on a WebView — embed the browser engine inside the app and draw the web app on top of it. iOS WKWebView and Android WebView are the stages.
- Cordova / Capacitor — web app + native plugin bridge.
- Ionic — Capacitor + UI kit.
- Tauri Mobile — WebView + Rust core for OS calls.
Strengths — web technologies (HTML, CSS, JS) work as-is. Bundles stay relatively small. Easy to share the same code with desktop and the web. Limits — small differences from native widgets in look and behaviour. Heavy animations and complex gestures can lag. iOS WebView policies and limited push notification support.
JavaScript bridge + native widgets — code written in a JS runtime composes native UI widgets.
- React Native — JS → native components. The new architecture (Fabric, TurboModules, JSI) reduces bridge overhead.
- NativeScript — JS calls native APIs directly.
Strengths — native UI look and feel. The React ecosystem (RN). Limits — platform-specific differences still end up as native code branches. Build environments (Xcode, Android SDK) are still required. Library compatibility.
Render directly onto an own canvas — instead of using platform widgets, draw every pixel through a custom graphics engine.
- Flutter — direct rendering with Skia → Impeller. Material and Cupertino widgets are also drawn internally.
Strengths — pixel-level consistency. 60 / 120 fps animations feel natural. Smooth hot reload. Limits — when the platform introduces new widgets, the framework has to follow. Accessibility and platform integration (system share sheets) need separate implementations. Bundle size is larger than other models.
3. Matrix
| Tool | Language | Rendering | Strengths | Limits |
|---|---|---|---|---|
| React Native | JS / TS | Native widgets | React ecosystem · familiar UI | Native module compatibility issues |
| Flutter | Dart | Own canvas | Consistency · performance | Large bundle · learning Dart |
| Capacitor / Ionic | JS / TS | WebView | Shares web code | WebView limits |
| Expo | JS / TS | On top of RN | Build · deploy automation | Managed constraints (eased by EAS) |
| Tauri Mobile | JS / TS + Rust | WebView | Small bundle · Rust safety | Mobile is alpha |
| Kotlin Multiplatform | Kotlin | Native or Compose | Code sharing + native UI option | Sharing UI needs extra tooling |
| .NET MAUI | C# | Native handlers | C# ecosystem · Visual Studio | Smaller community |
| Native (Swift · Kotlin) | Swift · Kotlin | Native | Best integration · accessibility | Two codebases |
4. Decision factors
Which experience for which user:
- Are the small look-and-feel differences between the two OSes part of the business value (native or RN).
- Do consistent pixels matter (Flutter).
- Want to share code with the web (WebView family).
Team's tech stack:
- React team — RN.
- Vue · Angular — Capacitor + Ionic.
- C# — MAUI.
- Kotlin / Java — KMP or native.
- Rust-friendly — Tauri Mobile (alpha, though).
Build · deploy — managed services (Expo EAS, Codemagic, Bitrise) cut down the burden of running build environments. Running native Xcode / Android Studio directly gives more freedom but more infra cost.
Library availability — check the maturity of libraries for core modules like in-app purchases (IAP), push, camera, and Bluetooth.
Accessibility · i18n — own-canvas tools need a separate mapping to the OS accessibility tree. Native widgets are usually stronger here.
5. Operations
iOS build requires macOS — building an iOS app needs Xcode, which only runs on macOS. Windows and Linux developers have options:
- macOS virtual machine or a Mac mini build server.
- Cloud build services (EAS, Codemagic, Bitrise, GitHub Actions macOS runner).
Android builds work on Windows, Mac, and Linux alike.
Code signing:
- iOS — Apple Developer account + certificate + provisioning profile.
- Android — keystore + Play App Signing.
For details see 03-android-build-apk.
OTA (Over-The-Air) updates — a model that ships JavaScript-only changes to user devices. The scope allowed under App Store and Play Store policies:
- Expo Updates / EAS Update.
- CodePush (Microsoft, sunset announced in 2024 → migration needed).
- expo-updates · self-hosted.
Platform policies allow business-logic tweaks but treat changes to the app's essential function as subject to store review.
6. Common pitfalls
Payments and subscriptions — IAP fee policies of the iOS App Store and Google Play shape the business model.
App store review — common rejection reasons on first submission are privacy policy, test accounts, and feature clarity.
Permission model — iOS and Android differ in permission timing and wording. The flow after a user denial differs too.
Background work — both OSes have aggressive battery policies, so periodic jobs and location often don't run as expected.
Push notifications — iOS background data messages have reliability limits.
Version fragmentation — iOS has fast adoption, Android has wide OS-version diversity. minSdk choice matters.
OS-update compatibility breaks — every year Apple and Google ship new SDKs and some libraries break. Check library maintenance activity.
Localization · regulation — GDPR, personal-data laws, children's data policies (COPPA). Review before a global launch.
Closing thoughts
Cross-platform tooling has different answers for different eras. The core decision is choosing among the WebView, JS-bridge, and own-canvas trade-offs to fit the environment. Team stack + build infra + library availability are the actual weights on the scale. Native is no longer the absolute answer, but it remains the right call where small differences carry business value.
Next
- flutter-basics
- android-build-apk
React Native official · Flutter official · Capacitor official · Tauri official · Kotlin Multiplatform · .NET MAUI official · Apple Developer App Store Review · Google Play Policy Center for reference.