Exploring the pitfalls of cross-platform solutions
The main objective of cross-platform technologies is to allow you to write code that can be used across platforms (Android, iOS, and the web). Due to this, you don't have to write separate code for the same feature multiple times, depending on the platform; the cross-platform framework will provide the tools for you to interpret this code and translate it into platform-specific versions. The power of the cross-platform framework depends on those tools that interpret and translate the cross-platform code into platform-specific code.
Let's learn what the assumed cross-platform development costs are and what you should know about cross-platform in general to avoid some common pitfalls.
Assumed cross-platform development costs
People often estimate cross-platform product costs by cutting the costs that are needed for native apps in half (or even into three, if there is a possibility of deploying the cross-platform app on the web too).
Under this assumption, our formula becomes as follows:
Here, n is the number of platforms and FC is the feature complexity.
Let's compare this to the costs of native development:
Looking at the preceding diagram and keeping the aforementioned assumption in mind, no wonder there is an increasing demand for cross-platform solutions.
Though this assumption may hold for greenfield projects, this probably won't be the case for real-world projects. To understand this, let's go over some of the currently available cross-platform technologies and how they work. We will review two of the most popular cross-platform frameworks: React Native and Flutter.
Text component in React Native will be converted into a
UITextView component on iOS and a
TextView component on Android. This sounds like a good approach and it is a plausible one, especially for developers coming from the Web/React world. But how does this conversion work and what are the tradeoffs and risks of React Native development?
Going back to our example, when a
- Performance: It's not native, especially for resource-intensive features.
- New features support: Because you're relying on React Native to provide support for new things, you can expect a bit of a delay.
There are also some application development specifics, such as permissions, notifications, in-app purchases, and media where you'd like more control over the native platform's API. In those cases, React Native lets you create native modules in regular native code, though it's not the primary purpose of the framework. If you arrive at a point where you need a native module, which is likely unless you have a really simple app, you will face the following issues:
- As a Client: Every roadblock that pushes you toward implementing a native module means higher costs than writing the same feature with native solutions, simply because there is a need for native expertise. Plus, it has to be integrated with React Native as well.
We'll update our charts and calculations in a moment, but first, let's check out Flutter.
Flutter is an open source UI software development kit that's developed by Google and used for developing cross-platform applications. It has three layers from an architectural perspective – the framework, the engine, and the platform – and relies on Dart's language specifics, such as ahead-of-time compilation.
As a developer, you interact with the framework and you write the app and the widgets (UI components in Flutter) in a declarative way using Dart, which the engine then renders to a canvas called Skia Canvas. This canvas is then sent to the native platforms: Android, iOS, or the web. The native platform will show the canvas and send the occurring events back:
Flutter's architecture may be similar to React Native, but there is a big difference in terms of performance. One key component of how Flutter achieves better performance than React Native is by going one level lower on the native side, meaning that it doesn't use the traditional SDKs that are used by native developers. Instead, it uses SDKs that need more developer expertise and can offer higher performance. Flutter uses Android's Native Development Kit (NDK) and iOS's Low-Level Virtual Machine (LLVM) to compile the C/C++ code coming from the engine.
While Flutter has pretty good performance compared to native and is far better than React Native when it comes to compiling the Dart code into a lower level native code (a key performance component), it also has a drawback: the cost of writing native code with Flutter is higher than using React Native to do the same.
At the time of writing, if you don't have support for a certain piece of functionality in the Flutter framework itself, you can write regular Java/Kotlin and Obj-C/Swift code, but you'll have to communicate with the Dart code through a channel, sending data through
Map in Dart,
HashMap in Java/Kotlin, and
Dictionary in Swift. If we compare this to the regular Java <-> Kotlin or Obj-C <-> Swift interoperability, this can be perceived more as a workaround than a scalable solution.
Both the Flutter and the React Native descriptions only serve as high-level overviews to help you understand how cross-platform solutions are designed and what to expect when you're working with them. To get a clearer picture, please read the official documentation.
To conclude our cross-platform overview, let's summarize the patterns that we observed in the aforementioned frameworks and see how we can update the general assumption of cross-platform solutions when it comes to estimating the costs of development.
The main ideology of cross-platform technologies is that you write the same code for Android and iOS (and the web); the framework provides the tooling to interpret this code and translate it into the platform-specific version.
While they do provide solutions for writing native code where needed, they are suboptimal and the goal of any cross-platform project is to avoid situations where interoperability with native code is needed.
This way, you rely heavily on the framework to make good decisions on your behalf when you're translating the cross-platform code into the platform-specific version. In short, all of these frameworks have, or will have, a tough time keeping up-to-date with both Android and iOS, two platforms that don't have an incentive to stay in sync with each other.
So, unless you plan on accepting big compromises, your cost of maintaining an acceptable level of nativeness will be relatively high with any cross-platform solution.
Actual cross-platform technology costs
Cost of development (n) = FC * (1 + Cost of going Native)
Here, n is the number of platforms and FC is the feature complexity.
The Cost of going Native can depend on a variety of things:
- How much interoperability the cross-platform technology provides with native. We've seen that this isn't optimal with neither of the aforementioned technologies.
- The knowledge gap between the cross-platform and native languages. You'll likely observe that expertise hardly translates from cross-platform to native.
- The more you need to dive into native implementations, the more your costs will compound because synchronization costs will kick in for the native code as well.
For visualization purposes, a more likely scenario of the costs associated with cross-platform development could look like this:
To conclude, if I were to write a project for myself, I'd consider Flutter. If it is a simple project where I don't have to cover any platform-dependent stuff (permissions, notifications, in-app purchases), just basic CRUD operations with a backend, a local database, and some nice UI stuff, then I'd probably go with Flutter. Otherwise, I'd use a native solution. Knowing how platform-specific things such as permission handling change on Android, I wouldn't dare trust a third-party framework to keep up-to-date.
That being said, cross-platform will probably still attract many start-ups in the future, due to the nature of start-ups accepting higher compromises to survive and achieve their short-term financial goals or to be product-market fit, which requires moving fast. However, there is another option: the multiplatform approach. This is cost-friendly both long and short term, and it is a sane approach from all perspectives.