In search of the Holy Grail or thoughts on cross-platform
30.10.2020
Today, only the laziest/richest business does not dream of saving resources and time on software development, and one of the most obvious ways to do this is cross-platform client development. Many are looking for this philosopher's stone to turn two or even three teams that duplicate each other into one.
From a development perspective, I want to cry every time I see two or three independent teams implement the same feature. And if different teams have complex solutions with similar bugs, then I start to cry out loud.
Today, there are several solutions that can help reduce duplication and create cross-platform solutions, but none of them has yet become the de facto standard. An experienced developer may argue that a standard is a utopia and that it will never happen, but there are examples of solutions that the industry has unanimously said "yes": Linux, Git, Docker, and others.
What cross-platform solutions exist today?
- C/C++ native libraries
- QT (1995)
- PhoneGap/Cordova (2009, 2011 Adobe)
- React Native (2015, Facebook)
- Xamarin (2011, 2016 Microsoft)
- Flutter (2017)
- Kotlin/Native, Kotlin/Multiplatform (2017)
- and others (Ionic, Sencha, ...)
С/C++
The oldest approach, reliable, but clearly not the easiest.
Once I oversaw an attempt to make such a client, and the first thing I faced was that it was not easy to find developers. Also, many things that we are used to in other tech stacks are missing in libraries and tooling.
Today, I would definitely not choose this path, it is too thorny. Besides, many people simply do not want to write C++, and I do not blame them.
Dropbox, which chose a similar approach in 2013, decided to abandon it in 2019. In short, they tried to write common things in C++, but faced such problems:
- development overhead
- lack of necessary tooling
- hiring and expertise problems
The difference in platforms even reached the common C++ library, which makes life much more difficult. In the end, according to Dropbox, all the benefits of code reuse are virtually nullified. You can read more about their experience here.
Based on the code of the mobile clients, Telegram also uses this approach. But to my taste, their clients are written very specifically. In the Android client code, I have noticed several places that seem to be intentionally partially obfuscated. If so, then everything is fine.
From the product stand point, Telegram is very cool, but there are many technical questions. In their solutions, almost everything screams that they are "brilliant mathematicians". For example, here is a very convincing criticism of their protocol.
React Native
In this approach, you are essentially writing a React application. React Native runs in the background, interprets your JavaScript code, and provides interaction with the device.
Airbnb was one of the first companies to seriously use React Native in production in 2016.
In 2018, they released a series of articles where they said that they were abandoning the framework in favor of native development for a number of reasons. However, they did not give up on the utopian idea of a single codebase for multiple platforms, hinting at other solutions that are gaining popularity.
Among the well-known applications that are fully or partially written in React Native, we can note the Instagram and Skype clients.
PhoneGap/Cordova
There are almost no major applications or companies that have bet on this framework. The only known example to me is Untappd (a beer social network).
So, I would not bet on this "white boxer" either.
Xamarin
Xamarin has a portfolio of fairly successful applications. For example, the American UPS. Or my favorite password manager Bitwarden, which I use often on my smartphone, and I don't experience any negativity.
However, many people note the difficulty of maintenance and the constant struggle with windmills in software at the junction of Xamarin and native platforms. However, if you are familiar with C# and the .Net infrastructure, then you can try using it for a prototype or MVP.
Flutter
A framework developed by Google allows you to write applications in the Dart language, which renders UI not with native platform tools, but in its own way, written in C++, similar to game engines.
Technology is starting to be adopted by major players (Yandex, Alibaba, and others), although the infrastructure is frankly raw and everything needs to be implemented from scratch. Google continues to actively develop this approach, quite successfully investing resources in marketing.
Among the successful cases are Google Ads, Yandex.Lavka, and eBay Motors. Or Wolt, which recently made a new application entirely on Flutter.
There are different opinions about Flutter. Our Lord & Savior Jake Wharton expressed himself on this topic as follows. I also remember reading his thoughts that mimicking each platform is a dead end.
My expertise in Flutter ends with launching simple hello world's and demos.
But since this platform is on the rise now, I asked for help from an expert in this field @otopba1. He is the sponsor of further tweets about Flutter, for which I am very grateful.
A finger in every pie
As you know, Flutter is trying to become a super-ultra solution, on all platforms from watches to TVs. Google is so pushing for this idea that at one of their presentations, they showed Flutter Octopus, the ability to run and debug code on multiple devices at once.
Of course, it's cool, but the entire code is cluttered with conditions like Platform.isAndroid
. Some methods from libraries and frameworks only work on certain platforms, and some even throw exceptions if called on an unintended platform.
The problem is not with you. The problem is with me
Flutter is growing rapidly. That's great. But with the number of new features comes an incredible number of bugs.
https://github.com/flutter/flutter/issues
Bugs of all kinds constantly pop up, from native crashes to eternally broken fonts on iOS.
It gets overwhelming
If you are writing a more or less large Flutter application, be prepared to contribute to Flutter plugins. This is a repository with the most basic libraries, such as push notifications. Most of the plugins have version 0.x.
You will constantly have to fork and add to the libraries. And if your library works with platform features, be kind enough to also write platform code, in addition to Dart.
https://github.com/flutter/plugins
A conversation of the mute with the deaf
If you think that you can write all the missing features of the essential plugins, submit a pull request, and ride into the master branch like a knight in shining armor, you are wrong.
The Flutter team is a master at ignoring pull requests. Sometimes it seems that the Google team only accepts requests that are no more than 5 lines long. Pull requests hang for years, and jokes are already appearing in the comments.
https://github.com/flutter/plugins/pull/1721
If the tool works well, you should praise the hands
Indexing constantly fails, the studio eats gigabytes of memory, Gradle sometimes falls into an infinite sync, and other exciting moments from the life of a Flutter developer
Одним махом семерых побивахом
In Flutter, widgets are either immutable (StatelessWidget) or mutable (StatefulWidget). The idea of StatefulWidget is to avoid redrawing the screen unnecessarily if the state has not changed.
But sooner or later, every Flutter developer comes to the realization that updating one widget redraws the entire screen.
At this point, the developer starts to add checks to the code to determine when to update the widget, when not to update it, or they start using another framework or writing their own approach to state management.
Language is the road map of a culture
Dart is a typed language, but not really. The compiler won't complain if you forget to return something somewhere or don't specify the type of an object. The IDE won't always tell you either.
Tweedledum and Tweedledee
No matter how much Flutter tries to look like a native app, the physics, system element rendering, and other things are still different from native apps.
Periodically, there is a feeling that you have a fake in your hands, not a real app. The Flutter team is constantly improving the behavior of the framework, but the feeling is that they will never achieve a 1-to-1 match.
Anything for a quiet life
Dart is a single-threaded language. It's great that you can do some operations directly in the main thread, and it doesn't seem to affect anything. But that's only at first glance.
At some point, the application will definitely start to lag, you will have to move heavy work to separate isolates and generally monitor the execution of various methods.
We are doing a great job, and we have done everything neatly. But I would like to remind you that most of the libraries are versions 0.x and not all the developers are concerned about the speed of your application.
Pros
- truly cross-platform
- developing rapidly
- large community
- hot reload works very quickly
- easy and quick to prototype, UI is convenient to make
- very fast and easy-to-use animations
- Dart is a super simple language
- Flutter comes with a large set of tools: for profiling, inspecting layout, etc.
- it is not necessary to write the entire application in Flutter. You can embed a piece of Flutter into native or vice versa
Kotlin Multiplatform
Kotlin Multiplatform is the most reasonable approach to cross-platform development.
It lacks most of the drawbacks of other approaches, the most obvious of which are:
- it does not require creating a new UI or system components
- it does not require switching to a completely new infrastructure
- it allows for a gradual transition
With this approach, the UI remains native to the platform. Work with system components, sensors, and other things is also native.
And all the rest, if desired, can be extracted into common modules:
- entities
- business logic
- storage
- networking
This is not as simple as it seems. To build such an application, you need to have a fairly clear understanding of how to build a common library, which things to put into expect/actual, and how to interact with subcomponents.
This is actually what the consulting company, Kotlin Multiplatform evangelist Touchlab, makes money on.
But if you have ever made a library/SDK that was used for different clients (even for one platform), then this is probably not a big problem for you.
A major stumbling block was the differing memory models for JVM and native. Due to this, a strict thread-confined frozen
concurrency was adopted. But recently, the Kotlin team announced changes, and they will soon do it differently.
You can read more about the new approach to memory management here:
blog.jetbrains.com/kotlin/2020/07/kotlin-native-memory-management-roadmap
medium.com/@kpgalligan/kotlin-native-concurrency-changes-bbb1a5147e6
The biggest drawback of KMP is that it is still very raw and immature. However, Cash App from Square is already written with this approach. And a number of other applications: Space from JetBrains, VMWare, and others.
I would like to use common logic and entities between mobile and web clients. But the web is not very good in this approach yet. Apparently, very few resources have been allocated to this, everything has been thrown to mobile platforms.
You can compile all of this into JavaScript and run it, as Jake Wharton did in SdkSearch. But you cannot use this approach to embed it in other frontend frameworks. More precisely, you can, but only for internal needs and harsh admin panels, where a huge bundle does not matter.
I would like to compile a common library with logic and simply import it into an existing Vue or React project to make calls. But it is not so simple now.
Here people talk a bit about the same.
Various libraries are developing quite rapidly to make life easier in the KMP infrastructure: Ktor (http-client & server), Serizalization (with support for JSON, Protobuf, CBOR), and others.
But the library I like the most is SqlDelight – a library for working with a database. Before meeting it, Room seemed to me a wonderful thing, but now SqlDelight seems to be a more convenient, thoughtful, and feature-rich tool.
In summary
The Holy Grail has not yet been found. But many are trying very hard to make a good cross-platform solution. Well, who will win, time will tell.
I personally lean towards solutions like C++ or Kotlin, where UI and logic live separately. It seems that this is the least of evils on the medium and long distances. Naturally, with a clear preference for Kotlin.
Solutions like Flutter are tempting, but I don't want to waste my energy fighting windmills at the intersection of technologies. Although a friend from HoReCa said that their "mobile developers" are happy after the transition. And @otopba1 seems to be quite happy with the use.