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.

React Native

As a development framework, React Native aims to consolidate and simplify native app development through a shared codebase written in a common language—Javascript. So instead of having to maintain code on two platforms with two separate teams, the idea is that you can maintain a single codebase and still deliver a native-quality experience to your users. That is, after all, the goal of any cross-platform toolkit (Xamarin and NativeScript included).

From my perspective, it helps some to understand the origin of the framework. React Native started as an internal project at Facebook and was made publically available in 2013, being fully open-sourced in 2015. The fundamental goals of the project were to allow for more streamlined cross-platform development on mobile, by extending web tools to the mobile space. This should not be the least bit surprising. After all, Facebook itself began as a web application, had a large swath of Javascript talent, and had also already invested heavily in ReactJS. So it was a natural next step to extend ReactJS to mobile in the form of React Native. But this illustrates an interesting point: React Native is best suited for web teams, or web applications, looking to chart a more direct and manageable path into the native mobile space. Ash Furrow summed it up nicely by saying that “the main selling point of React Native is that it lets you use React.” In fact, the stated goal of React Native was explicitly to bring modern web techniques to mobile, and to solve Facebook’s problems in this domain. And in that respect, it seems to have fared quite well.

"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.

Apps that have used React Native
Apps that have used React Native

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.

"Learn once, write everywhere"

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).   

This is a clear case where the technology drove the solution, because the problem was how to leverage a powerful technology to get more done. And on that note, I would argue that the ideal use-cases for React Native are the same: Are you trying to leverage existing Javascript—or better yet, React—expertise? Are there practical cases for shared components across web and mobile? Is it worth the risk of additional time to sort out compatibility issues?

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.

TL;DR Criteria:

  • App has a significant shared experience with all or some of the web
  • There is a strong desire to maintain code within a single team skilled in Javascript, and in particular, a team skilled in React
  • 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.

Flutter

The stated aim of Flutter is somewhat different, and so are its origins. It too aspires to significantly improve development time, but while maintaining expressive and highly performant UIs. Flutter was created by Google and was first revealed in early 2018. But by the end of the year had already reached v1.0 stability (and at the time of this article is rapidly approaching v2.0). But what’s most interesting to note is that the original purpose of Flutter has less to do with extending modern web techniques to mobile development, and more about addressing the need for a native solution across platforms. Unlike React Native, which relies on Javascript and ReactJS, Flutter uses a language called Dart, which was selected expressly for this purpose

While Dart as a programming language has been around for some time, it was repurposed for use with Flutter because of its unique ability to support both just-in-time and ahead-of-time compilation. This allows for some pretty neat workflow advantages, such as the ability to see code changes update in real time (known as “hot reload”) behaving like an interpreted language, while also having a strict type system and compiling to native bytecode. Being a type safe language, Dart also brings a more rigorous and straightforward workflow that is familiar to mobile developers, without the need for additional frameworks that add this capability to the language. By comparison, in order to add static type checking to Javascript, React developers must rely on a language extension (the two most popular being Flow and Typescript).

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.

TL;DR Criteria:

  • 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.

Finally, it’s worth noting that finding a Dart developer is probably just as challenging as finding a developer who can support both Swift and Kotlin, and certainly more difficult than finding a separate Swift and Kotlin developer. The latter, of course, also being more costly. On the other hand, this isn’t as much of a focal issue for React Native, as the stated aim of it is to consolidate around a single set of frameworks (React) and a single language (Javascript). As it happens, Javascript developers are plentiful, despite the fact that finding one who is actually skilled in React might be less so.

Native

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.

TL;DR Criteria:

  • 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.

Conclusion

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.

React Native was designed with the idea of allowing web developers comfortable with Javascript the ability to develop for native experiences without having to learn additional languages or toolkits—meaning that a competent React developer could leverage their existing expertise to build native apps. Flutter, on the other hand, was born from the need for a unified tool that could more quickly produce experiences across multiple platforms, but that requires the use of a separate and dedicated language to serve these needs.

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.

Recent Articles

Previous post Stoic: The Best Mental Health Tracker September 4, 2019
Next post Design Challenge: Week 12 September 12, 2019