Key Takeaways
- Choose mobile app development frameworks around team skills, delivery deadlines, and required device capabilities—not a universal performance ranking.
- Separate code sharing from rendering: React Native uses native components, Flutter draws its own UI, and Ionic typically runs web UI inside a native container.
- Treat PWA installation, offline behavior, and device API access as separate requirements with separate compatibility checks.
- Native development deserves priority when close platform integration or demanding performance requirements define the product.
- Budget for platform-specific testing, integrations, upgrades, and release work even when most application code is shared.
The best mobile app development tools are the ones that meet your app's requirements with a maintenance burden your team can sustain. Native development, shared-code frameworks, hybrid apps, and Progressive Web Apps each solve different problems; none is best for every project.
Start with the capabilities and distribution channels you need. Then choose how much code to share and which rendering model fits your product.
How Mobile App Development Frameworks Differ
Cross-platform describes sharing code across operating systems. Hybrid usually describes web UI running inside a native application container. These aren't competing categories: an Ionic/Capacitor application can be both hybrid and cross-platform.
Also distinguish native packaging from native UI. An app can be installed from a store without rendering its screens using platform UI components. The documentation linked in the following sections explains each implementation model.
| Approach | Rendering model | Typical code sharing | Device access | Distribution | Platform-specific work |
|---|---|---|---|---|---|
| Native | Platform UI frameworks | Separate platform UIs; shared contracts or libraries where appropriate | Platform SDKs directly | Platform app packages and stores | Each platform's implementation and release pipeline |
| React Native | Native platform components | JavaScript/TypeScript logic and UI definitions | Framework APIs, libraries, custom native modules | Platform app packages and stores | Integrations, UI differences, native dependencies |
| Flutter | Flutter-rendered widgets | Dart logic and UI | Plugins and platform interoperability | Platform app packages and stores | Integrations, platform behavior, embedded native views |
| .NET MAUI | Platform controls through handlers | C# logic and shared UI | Cross-platform APIs and platform code | Platform app packages and stores | Handlers, integrations, platform configuration |
| Kotlin Multiplatform | Native UI or optional shared Compose UI | Selected logic, or logic and UI | Platform-specific implementations and libraries | Platform app packages and stores | Separate UIs when keeping UI native; interoperability |
| Ionic/Capacitor | Web UI in a WebView | HTML, CSS, JavaScript/TypeScript | Web APIs and native plugins | Platform app packages; web deployment also possible | Plugins, permissions, WebView behavior |
| PWA | Browser-rendered web UI | Web application code | Supported browser APIs | URL and browser installation; store packaging is separate | Browser and OS compatibility |
Cross-Platform Apps
Shared code can reduce duplicated implementation, but “write once, use everywhere” is the wrong planning assumption. Decide what should be shared—business logic, UI, or both—and explicitly reserve time for platform differences.
React Native
React Native lets you describe screens with React while its native components create corresponding Android and iOS views. JavaScript execution and UI rendering are separate concerns: using JavaScript doesn't mean the app displays a website.
The architecture has changed substantially since early React Native. The New Architecture became the default in 0.76 and the only runtime architecture in 0.82. Hermes V1 became the default JavaScript engine in 0.84. Describing modern React Native primarily as an asynchronous bridge is outdated.
For a React team, it's a sensible shortlist candidate. However, web component markup doesn't transfer directly, and native library compatibility still matters. Audit your hardest integrations before choosing it; a missing or poorly maintained library can become your team's native-code responsibility.
Expo: Development Tooling Without a Native-Code Dead End
Expo is a significant React Native development option, not a separate rendering framework. Its development builds support custom native libraries and code; Expo Go has a fixed set of native libraries and isn't the ceiling of what Expo applications can do.
You can automate native project configuration with config plugins and generate projects through Expo's tooling, or maintain native projects directly. That escape hatch adds responsibility: changing native dependencies requires rebuilding the native app. Expo Application Services also provides cloud build, store submission, and update services. Evaluate those services separately from the framework choice.
Flutter
Flutter shares Dart application code and UI across platforms. Unlike React Native, it generally renders widgets with its own engine rather than mapping each widget to a system-provided control.
This makes Flutter worth evaluating when a consistent, custom visual design matters more than reproducing each platform's controls exactly. Its architecture supports native plugins, platform channels, and embedded platform views. Test those integration points, accessibility, text input, and scrolling behavior with your actual screens—not just a polished demo.
For Flutter vs React Native, compare your team's React experience against its willingness to adopt Dart, then prototype the UI and SDK integrations that carry the most risk.
.NET MAUI
.NET MAUI provides shared C# application development, commonly with XAML UI, and maps controls to platform implementations through handlers. Its supported platforms include Android, iOS, Windows, and macOS through Mac Catalyst.
It's worth shortlisting for an established .NET team, especially when desktop targets matter too. Validate third-party controls and platform-specific behavior, and check the MAUI support policy rather than assuming its support window matches the underlying .NET runtime.
Kotlin Multiplatform
Kotlin Multiplatform lets you share business logic while keeping separate native UIs. For example, you can share networking, validation, and data handling while building the iOS interface with SwiftUI and the Android interface with Jetpack Compose.
Compose Multiplatform also offers shared UI, but you don't have to adopt it to benefit from shared Kotlin code. Evaluate logic-only sharing when platform fidelity matters or you already have two established apps. The tradeoff is explicit: keeping native UIs means continuing to maintain two UI implementations.
Hybrid Apps: Ionic and Capacitor
Ionic supplies web-based UI components with Angular, React, and Vue integrations. Capacitor supplies the native runtime and plugin path for packaging web applications on iOS and Android. In this arrangement, a WebView—the platform's embedded browser view—renders the interface.
That can be a practical fit for a web team building forms, account management, catalogs, or internal workflows. Existing web UI can be more directly reusable than it would be in React Native. Our React Native vs Ionic comparison explores that distinction further.
Don't assume accessing a camera makes a hybrid app slow. Capacitor's camera plugin exposes device functionality through native implementations. Benchmark capture, data transfer, processing, and screen updates separately: taking an occasional photo is a different workload from processing every frame of a live video stream.
Likewise, don't assume every hybrid UI is slower than every native UI. Test large lists, complex layouts, animations, and heavy computation on representative devices. Your workload matters more than the label.
Progressive Web Apps
A Progressive Web App is a web application that can offer installation and app-like behavior through supported browser features. Start with a useful mobile website and responsive web design; then assess installation, offline operation, and device integrations independently.
Installation and Distribution
Installation differs by browser and operating system. Chrome on Android offers browser installation controls; Safari on iOS provides Add to Home Screen. Don't assume an Android-style in-page install prompt works on iOS.
Browser installation doesn't create an App Store listing. Store distribution requires a separate packaging and submission strategy, and Apple's App Review Guidelines include minimum-functionality requirements. A thin website wrapper isn't a guaranteed route into the store.
Offline and Background Behavior
Installation doesn't automatically make an app work offline. Service workers can cache resources and handle supported background events, but browsers can stop them; they aren't continuously running background processes.
Design local data, queued writes, retries, and conflict handling explicitly. Verify any background synchronization requirement on every target browser rather than treating “PWA” as a compatibility guarantee.
Camera, Notifications, and Other Device APIs
- Camera and microphone: getUserMedia() provides access in supporting browsers, requires a secure context, and requests user permission.
- Notifications: On iOS and iPadOS, Home Screen web apps support Web Push; requesting permission requires a direct user interaction.
- Bluetooth: Web Bluetooth is available in Chrome on Android but not Safari on iOS. An accessory integration that works in one isn't evidence it works in the other.
A PWA can be a strong choice for a feature-rich product when its required capabilities are supported. Reject it for a specific missing API, background requirement, or distribution constraint—not because “web apps can't do much.”
Native Apps
For new platform-specific work, start by evaluating Swift and SwiftUI on iOS and Kotlin on Android. UIKit remains relevant on iOS, while Jetpack Compose is Android's recommended modern native UI toolkit. Objective-C and Java remain relevant to existing applications and libraries; they aren't interchangeable recommendations for every new project.
Native development gives you direct access to platform SDKs without waiting for a cross-platform wrapper. Prioritize it when close integration with platform features, platform-specific interaction details, or a demanding performance requirement defines the app.
The cost is maintaining platform implementations: screens, navigation, integrations, tests, and release configuration can diverge. Don't assume that means exactly twice the effort, but don't budget for one implementation either. Shared backend contracts and reusable business logic can still reduce duplication.
Native isn't an automatic performance victory, and cross-platform isn't an automatic disqualification. Prototype the critical workload and decide whether a shared UI plus a native module is sufficient before committing to two complete implementations.
Which Option is Right for You?
Start With Team Skills
React experience makes React Native worth testing; web UI experience favors an Ionic/Capacitor or PWA evaluation; C# experience makes MAUI relevant. Include native debugging ability, local hiring options, and ownership of custom integrations—not just language familiarity.
Test the Deadline Against Real Work
A shared UI may reduce duplicated feature work, but an unfamiliar framework or unsupported SDK can erase that advantage. Estimate delivery using a representative feature, including permissions, device testing, signing, and release preparation.
Turn App Requirements Into Selection Gates
- URL-first access with supported browser capabilities: evaluate a PWA before committing to store packages.
- Existing web UI plus store distribution: prototype Ionic/Capacitor, including its hardest native plugin.
- Shared mobile UI: compare React Native and Flutter using team fit, UI fidelity, and integration maturity.
- Existing native UIs with duplicated business logic: evaluate Kotlin Multiplatform without a UI rewrite.
- Deep platform integration or demanding sustained workloads: prototype native implementation first, then assess whether a framework can host it effectively.
Before committing, build one end-to-end flow in your finalists: authentication, the hardest device integration, offline failure and recovery, and an accessible screen on both platforms. Record what required platform-specific code and who will maintain it.
Common Mistakes
- Choosing by a synthetic performance ranking: measure your startup, scrolling, processing, memory, and battery requirements instead.
- Counting shared lines as the outcome: optimize for delivery and maintainability, not maximum reuse.
- Trusting a plugin's existence: inspect maintenance activity, supported platforms, release compatibility, and an escape hatch if its maintainer stops.
- Confusing installation with capability: verify offline behavior, notifications, background work, and hardware access separately.
Production Checklist
- Accessibility and fidelity: test screen readers, large text, focus order, keyboards, navigation, and platform conventions on real devices.
- Offline behavior: define retry, synchronization, conflict resolution, and recovery after interrupted operations.
- Security: minimize permissions, protect credentials, and audit native plugins. For WebView apps, review Capacitor's security guidance on untrusted content and web security.
- Testing: combine shared logic tests with platform integration and end-to-end tests. The benefits of unit testing don't replace testing device behavior.
- Maintenance: assign ownership for framework upgrades, plugin replacements, SDK changes, signing, and store releases.
- Performance: validate release builds on representative lower-end hardware, not only a development simulator.
FAQ
What is the difference between native, cross-platform, and hybrid mobile apps?
Native apps use platform-specific SDKs and UI frameworks. Cross-platform describes sharing code across platforms; hybrid usually describes web UI inside a native container. A hybrid app can also be cross-platform.
Should I use React Native or Flutter for a new app?
Shortlist React Native when React skills and native platform components fit your team and product; shortlist Flutter when shared Dart UI and consistent custom rendering fit better. Validate your critical SDKs, accessibility, and release-build performance before choosing either.
Can a PWA access a phone's camera and other device features?
Yes, supported web APIs can provide camera, microphone, and other device access, subject to permissions and security requirements. Availability varies by browser and operating system, and installing the PWA doesn't remove those restrictions.
Is Xamarin still supported, and what should Xamarin apps migrate to?
Xamarin support ended on May 1, 2024. Xamarin.Forms apps should evaluate .NET MAUI, while Xamarin.Android and Xamarin.iOS apps can migrate to SDK-style .NET for Android and .NET for iOS. Audit dependencies and platform behavior before estimating the migration.
Does cross-platform development require platform-specific code?
Often, particularly for custom SDK integrations or different platform behavior. Even when libraries supply all the native code you need, you still need platform-specific configuration, testing, and release validation.
Further Reading
- React Native's New Architecture transition — why legacy bridge explanations no longer describe modern releases.
- Adding custom native code with Expo — development builds and native integration options.
- Flutter architectural overview — rendering, embedding, and platform interoperability.
- Kotlin Multiplatform getting started — logic-only and shared-UI approaches.
- Capacitor documentation — native containers, plugins, and platform workflows.
- Microsoft's Xamarin support policy — end-of-support details and migration destinations.
- Making PWAs installable — browser requirements and installation differences.
- PWA offline and background operation — service workers, caching, and execution constraints.
