Article in Mobile App Development category.
The Cross-Platform Chimera
There’s been a fair amount of publicity in recent years around the prospect of cross-platform development tools such as React Native and Flutter.
There’s been a fair amount of publicity and excitement in recent years around the prospect of cross-platform development tools such as React Native, and more recently, Flutter. And this is no surprise: mobile app development is complex business these days, and can get quite expensive. Without fail, one of the early questions we always hear on calls with prospective clients is, “so what do you think about React Native?” Unfortunately, that isn’t always an easy question for us to answer. Not because we don’t have an opinion on the subject, but because giving a sufficient answer depends on such a litany of factors. What usually raises this question for our clients is the knowledge that native development for both iOS and Android platforms is prohibitively costly, so they are hoping that shared code + smaller team + faster delivery = less $. But realistically speaking, this is rarely the case. I don’t mean to suggest that there aren’t optimizations to be had—there can be. But rather, the motivation is misplaced and they aren’t asking the right question: whether React Native (or Flutter) is right for their project.
But inasmuch as Fueled’s preference is for true, platform-specific, native development, both React Native and Flutter have become reasonably more popular lately, and the curiosity around them has continued to swell. Rather than simply discourage their consideration, we felt it was time to take a fresh look at the ideal case for each. My aim here is not to give a detailed exposition or comparison of these cross-platform tools—there are some good ones already out there—but rather to propose when, and why, either might be reasonably considered as an alternative to fully native development. Or put another way: when considering cross-platform options, what are the key indicators that inform whether it is in fact a low-risk approach that can deliver added value. Our hope here with this article is to provide a set of guidelines that our clients can apply to their particular product and organization to help better determine which approach is right for them when developing a mobile application.
The Declarative Advantage
Besides the prospect of a consolidated codebase, another major advantage that React Native and Flutter have over native development (as well as over other cross platform tools) is the ability to code a UI declaratively, allowing an experienced developer to work much more quickly than native methods have historically allowed. This approach is one of the reasons that React has soared in popularity in the first place.
Up until very recently, this was a huge advantage that realized workflow efficiencies unmatched by native, XML-based UI composition—despite major advancements in Xcode Interface Builder and the Android Studio Layout Editor. But this advantage may slowly lose ground as both Apple and Google have introduced SwiftUI and Jetpack Compose, which each introduce declarative prescriptions for UI composition.
"The main selling point of React Native is that it lets you use React."
Since then there have been quite a few apps to famously use React Native, and to great success (Artsy included). But there are an equal number of critical examples—perhaps most famously, Airbnb, who concluded that instead of the expected goal to write product code once instead of twice, “we wound up supporting code on three platforms instead of two.” This revelation is a telling one—not because React Native fails to live up to its promise, but because the false idea that React Native is “write once, run everywhere” is a dangerous one. And therein lies the risk of React Native as a suitable approach. If the app has its origins on the web and is going to be built in React, that added cost might be worthwhile. But if the aim is primarily to develop a single application that can easily be managed across both mobile platforms, it likely isn’t—and any expense saved to consolidate some of the codebase will quickly be exceeded by the cost to maintain the added complexities.
But I would argue that the decision to use React Native for any company seems to heavily rest upon their ability to mobilize it to serve the purposes of their organization. What this means is that it’s less about the consolidation of codebases as much as it is about the consolidation of teams, and the confluence of workflows across the organization. It’s worth noting that even in the relatively successful Artsy case, they have used it to better bridge web with a native app for iOS. They don’t (yet) even support a native app for Android!
React Native is actually a “learn once, write everywhere” platform in that its purpose—quite ingeniously—is to enable web developers, specifically those with experience in React, and companies with an existing investment in React, to extend their reach to native mobile.
React Native does aim to deliver more features more quickly across both platforms. However, that isn’t always the goal that an app developer is trying to achieve. It might make perfect sense for a platform like Facebook, or Artsy, Pinterest, or even eBay (though the latter, to my knowledge, does not use React Native).
For those who uniquely fit this criteria, there are also a number of performance compromises that one must either accept or dedicate additional resources to get right. These include the unnatural delay to app start-up time, a lack of real-time natural gesture handling, and a variety of rendering and navigation limitations, all brought about by the very nature of the React Native infrastructure.
But in addition to these performance concerns, in our experience, React Native is cumbersome and brittle, requiring a ton of overhead to support compatibilities amongst libraries, etc. and offers little advantage to smaller projects looking to get to market quickly. On the contrary, I would argue that React Native dividends are paid after the initial investment, making it better suited for larger projects looking to deploy frequent features quickly to all platforms.
- App has a significant shared experience with all or some of the web
- App does not require an excessive amount of custom bridging to native functionality (which can add complexity and cost)
- Performance limitations are either not applicable, worth accepting, or worth the extra effort (i.e. cost) to fix
The major risks to using React Native are not being able to properly capitalize on its benefits. Artsy has admitted that “maintaining, and keeping the infrastructure up-to-date on the web side is a much bigger task and requires a lot more engineering time,” which has been our experience as well. The question is whether that maintenance effort is worth it given the value and benefit that React Native can offer. Given this observation, one might even argue that the only truly valid use-case for React Native is to extend an existing React codebase to deliver a native experience on mobile.
Also unlike React Native, Flutter does not bridge to native UI libraries but instead uses an independent graphics library called Skia to draw the UI. But not bridging to native UI libraries means that this added UI code needs to be included with the application bundle, making the size of the app install slightly larger. But the performance is much better. All this makes Flutter particularly well suited for apps that wish to have the same aesthetic and user experience flow since the two platforms share precisely the same UI layer. While Flutter is also well capable of providing a tailored experience per platform, the greater the experiences diverge the more divergent code there is to support.
Flutter seems to be more well suited for those simple projects that are looking to get to market quickly for both mobile platforms. While admittedly not as mature as React Native, it has grown quickly and has the weighty support of Google behind it. While there are considerably fewer case studies, those apps for which it has been used in production, such as the Hamilton app, have proven that it is more battle-tested than most people realize.
- Need to optimize for cross-platform delivery and still maintain a high level of performance, at potentially less cost
- App aims to maintain a custom aesthetic and a similar experience across both platforms
- Highly accurate representation of native UIKit is not a critical requirement
- App can continue to be supported by a team skilled in Dart + Flutter
All of this comes at an additional cost: the team supporting the app must have a mastery of Dart. While at first it seems like an attractive prospect to support one language instead of two, there is always a risk at some level of having to also (eventually) touch native development. While Flutter has a very robust package offering, in the event that some specific platform requirement isn’t addressed by an existing package, one must be created, which also requires additional native development. Any such requirements then start to effectively diminish the returns offered by Flutter, as platform-specific expertise starts to be required.
For our purposes, developing natively means using the respective languages and toolchains provided by each mobile platform. Using native toolchains means have the greatest flexibility to custom tailor an experience to the platform where the only constraints are imposed by the capabilities of the devices themselves. This is probably the primary reason why this is our go-to solution: we have the greatest ability to create unhindered mobile experiences.
For iOS, this means using Xcode with Cocoa Touch / UIKit, Swift or Objective-C, and at the lowest levels, C/C++. For Android, this means Android Studio using the Android SDK/NDK, Kotlin or Java, and possibly also C/C++. The advantages of this approach are clear: each app can be optimized and implemented to suit the particular platform and also capitalize on the latest advancements within each others’ respective SDKs. This is especially true if there’s a desire to implement cutting-edge features during the beta rollout period (May–August for Android, June–September for iOS) to be prepared for public release.
But this also typically means staffing bespoke teams that specialize in each platform and the programming languages used to code for them. Even in cases where that expertise rests with a single team, it still means roughly 2x the workload and supporting 2x the codebase. And that’s where things can get particularly expensive, especially when it comes to maintaining each of those platforms long-term.
- App performance is mission-critical and the experience is tailored per platform
- App uses hardware-level services or cutting-edge frameworks (such as ARKit or ARCore)
- There is an inclination towards advancing technologies and toolchains offered by the platform providers or native ecosystems (such as watchOS/WearOS, or porting iOS to MacOS)
Of course, there aren’t really many risks to using native toolchains because these are the primary ways to develop for those platforms. But the reality of the costs involved is understandably discouraging for some, both to initially develop and then to maintain. This is especially true given how common of a requirement it is to essentially develop the same app for two different platforms.
When selecting any technology, it’s important to first understand the problem that you are actually trying to solve. That is core to our product philosophy and process here at Fueled and it applies equally to the technology we choose.
There’s no question that a consolidated codebase means less to maintain with fewer team members required to support ongoing updates. And that’s an attractive prospect. But whatever the approach there is often a hidden cost driven by whatever risks may come along with it. While the cost of a pure native approach might at first be more obvious, the burden of risk of using any cross-platform toolkit will depend heavily on the nature of the project and how it will be supported long term. As unseen complexities grow within a project and toolsets continue to evolve on all fronts, this potential risk is always going to be somewhat less with a fully native approach, despite the costs involved.
Regardless of the approach, it’s important to weigh both short-term and long-term costs/benefits. That includes financial and practical considerations not only in bringing the initial mobile app to market, but providing ongoing delivery of new features and a plan for growing the right team to support. As each project, business and organization is different, so too are the considerations for how to best define and achieve success. To be clear, there are ideal use-cases for cross-platform development, but our stance is that those are much more specific than most clients realize.
Our clientele come to us for our expertise in designing and developing mobile apps, which does require extensive knowledge to do so. But let’s not ignore the wisdom required to select the best approach to implementation given their needs. After all, the end goal should always be to deliver optimal outcomes. Choosing the wrong approach can have consequences that are much more costly than a simple outlay of cash. Choose wisely.