Book Image

Simplifying Application Development with Kotlin Multiplatform Mobile

By : Róbert Nagy
Book Image

Simplifying Application Development with Kotlin Multiplatform Mobile

By: Róbert Nagy

Overview of this book

Sharing code between platforms can help developers gain a competitive edge, and Kotlin Multiplatform Mobile (KMM) offers a sensible way to do it. KMM helps mobile teams share code between Android and iOS in a flexible way, leaving room for native development. The book begins by helping you to gain a clear understanding of the Kotlin Multiplatform approach, how it works, and how it is different from cross-platform technologies, such as React Native and Flutter, and code sharing options, such as C++. You'll then see how your team can use this software development kit (SDK) to build native applications more effectively by learning timeless concepts and working through practical examples. As you advance, you'll get to grips with the core concepts, understand why UI sharing fails, and get hands-on with developing a small KMM application. Finally, you'll discover expert tips and best practices, along with production- and adoption-related questions, that will help you take the next step in your project and career. By the end of this Kotlin book, you'll have gained a solid understanding of the capabilities of KMM and be able to share code between Android and iOS flexibly.
Table of Contents (15 chapters)
1
Section 1 - Getting Started with Multiplatform Mobile Development Using Kotlin
5
Section 2 - Code Sharing between Android and iOS
10
Section 3 - Supercharging Yourself for the Next Steps

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:

Cost of development (n) = FC

Here, n is the number of platforms and FC is the feature complexity.

Let's compare this to the costs of native development:

Figure 1.3 – The cost of native development versus the cost of cross-platform development as a function of feature complexity

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.

React Native

React Native is an open source framework for developing mobile applications. It is based on the React library and converts React components and JavaScript code into native Android and iOS components. For example, a 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?

React Native creates a thread where it runs the respective JavaScript code, which communicates with the native code by running on the traditional main thread, through a bridge that asynchronously sends serializable data:

Figure 1.4 – The architecture of React Native

Going back to our example, when a UIView or TextView is clicked in the native component, the appropriate data is then sent through the bridge to the JavaScript code, and then back again. Now, if you're thinking about the performance costs of this bridge mechanism, then you're in the right place. Let's look at the drawbacks of React Native:

  • 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 Developer: If you planned to reuse your JavaScript and/or React knowledge to create mobile applications, you will have to acquire native mobile development skills anyway.
  • 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

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:

Figure 1.5 – The architecture of Flutter

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.

Important Note

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

Going back to our initial cross-platform costs assumption, we can update our formula with a new variable:

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:

Figure 1.6 – Cost of cross-platform development with potential roadblocks as a function of feature complexity

Again, the number of roadblocks you'll hit heavily depends on how much you're willing to compromise from nativeness and how much you're relying on platform-specific APIs.

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.