Mobile applications have become ubiquitous and essential over the past two decades, particularly for direct-to-consumer products. While Steve Jobs initially believed smartphones didn't need third-party apps, it's now clear that installable apps are indispensable as part of the marketing, engagement and retainment strategy for companies large and small. The mobile landscape has simplified to just two platforms—iOS and Android (farewell to Blackberry, Palm, and Windows Phone, in addition to some also-rans)—yet most apps still need to maintain a presence on both. For teams in their early stages, the primary challenge is the cost of developing for these two platforms as weighed against a cross-platfrom solution that attempts at broad reuse of the same code. Let’s explore the available options and their relative merits.
Native Development: The Traditional Approach
When developing natively for mobile platforms, developers must navigate two distinct ecosystems: iOS and Android.
For iOS development, Apple provides Swift—a modern, safe, and efficient programming language—along with SwiftUI, their latest declarative framework for building fluid, responsive user interfaces. Swift's focus on safety and clarity, combined with SwiftUI's intuitive approach to UI development, enables developers to create sophisticated applications that feel natural on Apple devices. Some libraries, especially those that have been around for a while, may include Objective-C, with idiosyncrasies for iOS, or more broadly across Apple platforms, which date back to the NextStep OS that Jobs brought back to Apple as the foundation for what was originally Mac OS X (now known as macOS) and proliferated across all the other flavors (eg. tvOS, watchOS, iPadOS, and visionOS). It’s taken a few years for SwiftUI to gain prominence, but the approach stabilized in the past two years, but it is still not fully backwards compatible, which requires developers to reach back into the older UIKit framework, which relies on an imperative approach, to cover some corner cases of Apple platform development.
Android’s foundation was built on Java, with its own unique challenges, such as performance of the JVM and how it handles garbage collection. For modern Android development, developers primarily use Kotlin, an expressive language with strong Java compatibility, and Jetpack Compose, Google's modern toolkit for building native UIs. Android there is still the occasional need to fall back to the legacy XML-based UI approach, such as supporting rich notifications on older versions
Jetpack Compose and SwiftUI both use a declarative approach, helping developers build complex interfaces with cleaner code while maintaining the performance and flexibility of native applications. This declarative paradigm is valuable as it does bring the platforms closer together by letting the developer “declare” what they want the UI to look like and leave it up to the system to render it appropriately.
While both native approaches offer the current best-in-class experience for each of the platforms, neither are very well easily translatable to the other. For example, Android's greater flexibility within the operating systems is not easily duplicated in iOS, but this comes at the cost of great complexity. Similarly Android, especially with Google’s layer that sits on top of the open-source platform (AOSP), has some stronger opinions on how certain things should be built, while Apple’s approach is to provide some guidance while also forcing the developer to do more of the heavy lifting at times.
While native development offers the best performance and full access to platform features, providing unparalleled speed and responsiveness along with seamless integration of device capabilities, it comes with some potential trade-offs: the necessity of maintaining entirely separate codebases for iOS and Android, increased development resources to support multiple platform-specific teams, and the added complexity of managing parallel development cycles and testing processes across both platforms. Depending on the complexity of the app, this approach may require a broader developer background to leverage the full extent of each platform. On the flip side, it usually allows the teams to stay as close to the platform without an extra layer of indirection that needs to be understood and can potentially lag behind the native platform.
This leads to an interesting set of product questions. Most notably, how do you create a single product experience that leverages both platforms to their maximum extent, even when those extents are mutually incompatible with each other?
Web-Based Solutions: The Browser Approach
For teams with web expertise or simpler app requirements, web-based solutions offer viable alternatives:
Progressive Web Apps (PWAs)
These modern web applications leverage standard web technologies to deliver app-like experiences directly through the browser, offering broad platform compatibility and simplified deployment. PWAs provide seamless accessibility across devices without requiring installation from app stores, while automatic updates ensure users always have the latest version.
Although they excel in reach and maintainability, PWAs face certain limitations regarding deep platform integration and may not match the full capabilities of native applications when it comes to hardware access and system features. Additionally, in the face of changing regulations, Apple temporarily signaled that PWAs would not be allowed on iOS home screens. While this decision was reversed within a couple of months, there still remains the possibility that PWAs will lose abilities or otherwise be constrained over the long term.
Hybrid Applications
By leveraging WebView-based solutions such as Cordova or Capacitor, development teams can create applications that seamlessly blend web technologies with native platform capabilities. These hybrid frameworks enable developers to write applications using familiar web technologies like HTML, CSS, and JavaScript while still accessing device features through a comprehensive plugin ecosystem. This approach allows for efficient development cycles by maintaining a single codebase that can target multiple platforms, while still providing access to platform-specific functionality when needed. The WebView container acts as a bridge between web content and native features, offering a balanced compromise between development efficiency and platform integration. This approach is optimal if the same components are needed to not only be available on both iOS and Android, but in a traditional web setting across desktop and mobile browsers as well.
Cross-Platform Frameworks: Universal Solutions
For teams seeking a single codebase approach, but with a desire to leverage system functionality and a more native-like appearance, several frameworks offer compelling options:
React Native
Originally developed by Facebook (now Meta) in 2015 to address their own cross-platform development needs, React Native has evolved into a popular open-source framework. The vast ecosystem of libraries and tools, combined with the familiarity of React development patterns, makes it particularly attractive for web developers transitioning to mobile development. While leveraging JavaScript/TypeScript (JS/TS) and providing native UI components, its journey hasn't been without challenges. Notable companies like Airbnb and Udacity have documented their experiences of adopting and later moving away from React Native, citing performance issues and maintenance overhead. However, the framework continues to evolve under open-source governance, with tools like Expo significantly simplifying development by providing a managed environment, pre-built components, and deployment solutions.
Flutter
Flutter is Google's modern UI toolkit utilizing the Dart programming language to achieve exceptional performance and streamlined developer productivity. Its innovative custom rendering engine, known as Skia, ensures pixel-perfect visual consistency across different platforms (not just mobile, but including desktop applications on macOS, Windows, and Linux) by directly handling all rendering operations. This approach allows Flutter to maintain precise control over every pixel on the screen, resulting in highly consistent and beautiful user interfaces that look and behave identically whether running on iOS or Android. Flutter's hot reload feature enables rapid iteration during development, while its widget-based architecture promotes code reusability and maintainable application structure. Flutter has gained significant popularity in developing economies (such as India), offering a cost-effective solution for cross-platform app development. However, recent developments have raised concerns within the Flutter community. In early 2024, Google implemented layoffs affecting key teams, including those working on Flutter and Dart. These reductions have led to uncertainty about the framework’s future and Google’s commitment to its ongoing development. Also, while Dart appears to be very similar to JS/TS, it is a different language with a limited set of experienced developers. As Gergely Orosz points out, the popularity of Flutter appears to have overtaken React Native, but it’s popularity depends on different geographies. Also, it shows that teams using Flutter have been less likely to generate revenue. It is hard to suggest why this is the case.
Other
Microsoft App UI (MAUI) is a solution enabling .NET developers to use C# while maintaining near-native performance through platform-specific optimizations. For teams already steeped in C# and the .NET platform, this choice can make sense. Especially if it allows for reusing business logic originating elsewhere outside the mobile app, it follows a similar idea as utilizing something like JS/TS in Node for the backend for React Native. It is built on Xamarin, which predates MAUI as Microsoft’s cross-platform mobile solution.
Qt is a comprehensive C++ framework that allows developers to create cross-platform mobile applications using a single codebase. It provides a rich set of libraries and tools for building native-looking UIs, along with extensive hardware access capabilities. While Qt offers strong performance and a mature ecosystem, it's less commonly used for mobile development compared to other frameworks, being more popular in Linux desktop and embedded systems development. In many ways, Qt moved in the opposite direction as Flutter and React Native; whereas the latter two started off focused on mobile, then web and desktop apps, Qt started primarily as a desktop solution that migrated into mobile.
Even more?
Additionally, other more obscure and less broadly used solutions also exist. It seems like every language that a developer might prefer eventually ends up with a framework for building mobile apps. For example, Rust developers might want to try Tauri, while Python developers could lean on Kivy.
So Why Bother with Native?
Keep reading with a 7-day free trial
Subscribe to Tricky Bits Substack to keep reading this post and get 7 days of free access to the full post archives.