Thesis: the decision to use a cross-platform framework is mostly a business one, and somewhat a technical one.
What Problem Is Being Solved?
Recently I've been building spreadsheets with the goal of comparing different aspects of iOS, Android, and the web. For example, here's a high-level comparison of the basic modern tools, languages, frameworks, and architectures employed on each:
Aspect | Android Native | iOS Native | ReactJS |
---|---|---|---|
IDE (Development Environment) | Android Studio | XCode | VS Code |
Project Creation Template | Android Studio templates | Xcode templates | npx create-vite |
Language | Kotlin | Swift | TypeScript |
UI Framework | Jetpack | SwiftUI | ReactJS |
Test Framework | JUnit | XCTest | Jest |
REST/Networking Tool | Retrofit | URLSession, Alamofire | Axios, fetch |
Persistence/Database Library | Room | SwiftData | |
Lint Tool | Android Lint, KTlint | SwiftLint | ESLint |
Reference Project on GitHub | Now in Android | Bulletproof React | |
DI Tool/Framework | Dagger | Needle | React Context |
Build Tool | Gradle | Xcode Build System | NPM |
Code Minifier | R8 | ld | Terser |
Asset Bundler | AAPT | actool, momc, ibtool | Webpack |
Async Image Loading Tool | AsyncImage (Coil) | AsyncImage (SwiftUI) | Browser |
Concurrency Framework | Coroutines | async/await (Swift) | async/await (JavaScript) |
Reactive Data Flow Framework | Kotlin Flow | Combine | RxJS |
Common UI Architecture | MVVM | MVVM | Components |
In short, that's a lot for any one person to become an expert in. Most of us struggle just to develop expertise in a single one of these platforms. So, as an engineering manager, you have essentially only three solutions to this problem.
- Hire specialists who can handle each platform
- Look for opportunities to "write once, run everywhere," so that you can do more with less
- Hire extremely senior people who have experience in all three platforms
It doesn't take a spreadsheet to figure out that (1) and (3) are going to be more expensive in the short term: hiring more people--or more people who are also quite senior--is going to cost. As a result, (2) looks pretty attractive.
How Tech Businesses Evolve
We need to also discuss, for a moment, the natural evolution of a tech startup.
Every business needs a website, and most businesses start with a web app. Websites run on every device, so you can get by with this for a bit, while you're proving viability (and thus, the ability to hire).
In the US, once the business looks viable, it makes sense to build an iOS app. The US' Disposable Income class is mostly using iOS, and these are the folks you need to target to make money. Independent of any interesting technology choices, apps can attain significantly higher engagement than the web. For one, the continued presence of your little launcher icon on an end user's device is free marketing. And more importantly, Push Notifications are the modern replacement of web marketing emails. Even many established apps continue to send notifications that are barely better than this:
"Have you thought of our product today? Open the app to engage and monetize."
The last client that US businesses build, when they've entered the growth/scale phase, is an Android app. As noted above, the Disposable Income class is mostly using iOS. And, it's also true that there are slightly fewer Android users in the US, anyway. All that aside, it is nonetheless true that you can greatly expand your addressable mobile market in the US by building an Android app. So US companies do. More launcher icons, more push notifications, more moolah.
Mid-late growth phase businesses are Android businesses. Outside of the US, UK, Canada, and Australia, everyone uses an Android phone. If you want to make money in Europe, Asia, Latin America, or Africa, you are in the Android business.
These phases of evolution are important to keep in mind because technical decisions (and hiring) in an engineering department often track these phases. Explicitly: you will likely make different choices about client platforms depending on which stage your business is in.
What Are the Options?
At a high level, you can either use cross-platform frameworks or not. Let's talk about the options that exist within the "okay, let's use them" category. There are three cross-platform frameworks worth considering right now: React Native, Flutter, and Kotlin Multiplatform. We can compare these along similar vectors to what we did above:
Aspect | KMP | Flutter | React Native |
---|---|---|---|
IDE (Development Environment) | IntelliJ IDEA | VS Code or IntelliJ IDEA | VS Code |
Project Creation Template | KMP Project Generator |
flutter create command |
react-native init |
Language | Kotlin | Dart | TypeScript |
UI Framework | Jetpack Compose, SwiftUI, React | Flutter | React |
Test Framework | Kotlin Test | flutter_test library | Jest |
REST/Networking Tool | Ktor | Dio, http | Axios, fetch |
Persistence/Database Library | SQLDelight | sqflite | AsyncStorage |
Lint Tool | Ktlint | Dart Linter | ESLint |
Reference Project on GitHub | People In Space | Flutter Samples | |
DI Tool/Framework | Koin | get_it | React Context |
Build Tool | Gradle |
flutter build command |
Metro |
Code Minifier | Platform-specific | Part of flutter build
|
Metro Bundler |
Asset Bundler | Platform-specific | Part of flutter build
|
Metro Bundler |
Async Image Loading Tool | Platform-specific | Image.network() | Image (React Native) |
Concurrency Framework | Coroutines | Dart async/await | JavaScript Promises |
Reactive Data Flow Framework | Flow | RxDart | RxJS |
Common UI Architecture | MVI | Widgets (MVU) | Components (MVU) |
React Native
Since every business starts with a stock of web developers, React Native is an attractive option since it's so similar to React.js. Your web engineers have to reach outside their comfort area a little bit, but can essentially be redeployed to mobile with a similar skill set. If your startup is not able to hire iOS specialists in the near term, this is a pretty good pathway to get those Push Notification bucks.
But React Native can be slow. And if your app needs any native iOS functionality, the bridging code can get unruly. It's also true that it's easier to hire native iOS engineers than it is to hire a web engineer and then train them to work on an iPhone. The closer your stay to the "default" platform recommended by Apple, the easier it will be to hire for iOS in the future, too. In short: if you have funding to scale your Engineering team over the next 6-12 months, and know that you'll be able to staff iOS engineers, don't mess around with RN.
Flutter
Flutter's Dart language gets compiled directly down to ARM machine code, which avoids one of React Native's biggest gripes. However, whoever is writing this app will need to learn Dart. This either means hiring specialists (we were trying to avoid this, right?) or having your web/iOS folks learn Dart. Fortunately, Dart is kind of like if TypeScript and Java had a bastard child. So, it's fairly accessible to a broader technical audience.
Like React Native, Flutter has a pretty good story around web interop. For these reasons, it wouldn't be outrageous to start a company with Flutter. You could have web, iOS-- and Android for free. However, most companies don't start with Flutter, they start with React.js on the web. Adopting Flutter once you already have a large React.js investment makes less sense: you'd probably either pick React Native for its similarity or just create a discrete iOS codebase.
Kotlin Multiplatform
Let me start by stating full-throatedly that Kotlin is the best front-end language. Line break.
I am saying this as an Android engineer, yes, but StackOverflow agrees with me. TypeScript is a nice improvement to JavaScript, but it is still a weird bolt-on language. Swift is 5,000 times better than Objective-C and is almost as good as Kotlin. But, Kotlin is a little newer. It had the benefit of learning from these languages, iterating on their weaknesses, and yielding a more polished result.
At the moment, I don't think it makes any sense to build web products with KMP, although it may in the future. There are really two camps that would think seriously about adopting KMM in my view:
- International startups that built an Android app first, and are now expanding to iOS
- American startups that have a web app, and are at their "how do we do mobile?" inflection point.
KMM makes a lot of sense outside the US, where the mobile economics are different. If you start with a modern Kotlin Android app, building a SwiftUI shim in KMM would be an awesome way to expand your userbase -- while not investing fully.
For the American web startup, KMM could still be attractive. KMM works in a progressive way, where you get to decide how much of a specific platform is implemented natively or not. So, you could decide "okay, we've got our React.js web app, but we're going to share some code across iOS and Android in this new codebase." Business logic could be written in Kotlin (a beautiful language), while large portions of the apps are built in Compose and Swift/SwiftUI. This allows you to stay somewhat true to the platforms, making long-term hiring for mobile specialists easier, too. (Dart and RN don't have this benefit.)
Shared Architecture, Separate Platforms
The last option I want to talk about-- and indeed the one that I think is right for many monetized companies-- is to maintain three separate frontend codebases that share architectural similarities.
When iOS, Android, and the Web first emerged, they were very different from one another. JavaScript is not much like Objective-C. And syntax aside, Java is not much like either of those two. jQuery isn't like UIKit, which isn't like writing XML for Android Views.
But that isn't the case anymore. React.js, SwiftUI, and Jetpack Compose all agree on composable hierarchies of UI components using uni-directional dataflow. TypeScript, Swift, and Kotlin all share some broadly similar syntax and language constructs. So, a valid question to ask is this:
Can we exploit the similarities in the three platforms to keep the codebases ideologically consistent?
There are some clear benefits to doing this. Firstly, it'll be easy to hire platform specialists in perpetuity, since you're not "doing anything weird" in your tech stack. Secondly, you'll have no performance hits, since everything is native. And thirdly, there'll be no added complexity on any individual platform when bridging from the cross-platform framework into the native code.
In practice, there are unique technical challenges to either approach - aligning three platforms through organizational cohesion or aligning three platforms through cross-platform frameworks. Cross-functional collaboration with your UX and Product designers becomes increasingly important when you operate in multiple codebases: the Engineering team needs to be using the same component names as the UX team, to stay aligned.
When to Choose Each
"It depends," is usually a copout of an answer. So I'm going to make some opinionated suggestions.
If you're starting from scratch and need to prove viability, either start with a React web app or start with Flutter, which are acceptable web solutions.
Next, if you didn't pick Flutter and are figuring out how to expand into mobile, you should pick:
- React Native, if you don't have a line of sight to fund specialists on your Engineering team over the next year.
- KMM, if you are in a market where Android dominates, and will enter the market on Android first.
- A native iOS app, if you are American, and will enter the market on iOS first.
More nuanced situations arise for SMBs, who already have some combination of the technologies above. But, generally speaking, if you are growing as a business and Engineering team, you should be migrating away from cross-platform frameworks into discrete native codebases that share similar architecture and concepts.
If you are a revenue-stable US SMB, and iOS is an important portfolio driver, your best investments are probably in native iOS or React Native, depending on the context of your web staff and product. If you are a revenue-stable SMB outside the US, your best investment is probably KMM.
There are yet other businesses that support more client platforms: for example, Netflix, Crunchyroll, etc. need to support a half-dozen-or-so TVs and Gaming platforms. To the extent that these companies can envelope a functional web app into some kind of wrapper while maintaining acceptable media performance, that option will almost certainly be a good trade of engineering effort and complexity vs. end-user monetization per platform.
Wait, What About Tech Factors?
At the beginning of this article, I argued that multi-platform is a business decision. And, my argument has essentially been that you need to look at monetization of iOS, Web, Android, and compare it to your Engineering team's budget. If you have a lot of money, build native and don't look back. If you don't, you can compromise and optimize for the platforms that are most important to driving revenue in your business.
But, technology can be an important aspect of "what drives revenue for your business?" If you intend to have a media-rich application with smooth animations, real-time widgets, and a high frame rate, you may not be able to achieve your business goals with some of these choices.
There are many high-profile deep dives around mobile teams entering and exiting the React Native arena - here are two well-known organizations that have come or gone:
Anecdotally, Shopify shed some significant native mobile headcount through regrettable attrition when they adopted RN at their established business.
For Flutter and KMP, it may be too early to have "here's our decision after a few years"-style articles from high-profile businesses. But, they also have their own naysayers:
While it is true that all of RN, Flutter, and KMP support bridging into native code, this type of code tends to invalidate the productivity benefits of adopting a cross-platform framework to begin with. Not only are folks writing native code anyway, but they also have the added complexity of fighting an additional framework, too.
Cross-platform is always a financial or organizational shortcut, inherently more cumbersome than native development. And while there is some developer productivity to be gained in code reuse, the gains may not be linear with the size of an app or its Engineering team. On the other hand, shortcuts are sometimes necessary in business for survival. If your options are to ship your web app on iOS via RN, or to lose market opportunities on iOS for lack of staffing/expertise: that's an obvious choice.
Well, that's it. Would love to hear your thoughts!
Top comments (1)
Very well explained :)
There's one "usecase" of KMP that I think is worth considering a bit more. Although I've seen it typically "accepted" as a solution with the goal of reducing costs, to me the real benefit is unifying logic and reducing bugs -due to a missmatch of logic between platforms. And also, forcing a more unified architecture and higher coordination between technology-teams.
If I think of using KMP to share logic (not UI), I see no reason to use "the pure native approach" in apps that target both platforms and are complex enough to warrant having at least one senior engineer. You have the same UX, the same performance (with few exceptions that are rarely noticeable by the user), and the same access to native APIs.
As I see it, the only downside of using KMP is that good architecture becomes essential instead of a nice to have; and you gain fewer bugs and, in theory, less cost.