Why is Flutter winning?

Last year, I have been writing Flutter apps for iOS and Android anyway. Before that, I had and have 5 years of experience with Xamarin. It has been a wonderful 5 years. Thanks to Xamarin and my love for this framework, I, in principle, moved to the camp of developers, this tool helped me earn a lot of money, knowledge and find wonderful colleagues. So why am I writing on Flutter now? Short answer, because Flutter covers all the needs of cross-platform development.


A bit of history


Correct me if I'm wrong, but 2009 was in many respects key for mobile development in general and cross-platform development in particular. In 2009, iPhone 3gs was released, which allowed you to run third-party applications from the AppStore. For the first time this opportunity appeared a year earlier in the iPhone 3g, but 3gs has become a truly massive, “popular” iPhone. Again, a year earlier, in September 2008, Android was introduced to the public and in 2009 many phone manufacturers began trying Android for their new phone models. In spring 2009, Nitobi introduced PhoneGap, a new framework for creating cross-platform applications based on HTML5, CSS and JS. In the same year, in SeptemberXimian released MonoTouch, which allowed you to write iOS applications using Mono and C #. In the same 2009, in December, Rovio Entertainment released a game for iOS and, for a moment, Maemo, which in many ways marked the beginning of the mobile game industry - Angry Birds. The last example here is not accidental.

The first cross-platform framework "for the people" can be considered PhoneGap (Qt developers, do not throw stones). It was a wonderful and very obvious idea - to bring the web into the world of mobile development. By 2009, the capabilities of the web had already begun to extend beyond the browser ( hello node.js), while writing web applications in JS was pretty straightforward. The second, no less important point is the rendering of the UI. The way the rendering happens lies with the browser engine and all these engines more or less follow the W3C standards for HTML, CSS and DOM. Any web developer who has made up a site expects that his site will look almostidentical in any browser, on any platform. This, in my opinion, is the most important aspect of the web as an open platform. Why should I learn a new language / framework for drawing UI for each of the platforms, if for a long time there is a standard for modeling UI for different browsers.

After that, Cordova spun off from PhoneGap, and from it Ionic. It would seem that this is an ideal framework, but there were 2 points: performance and OS integration. One of the main goals or, if you want, application benchmarks, written on cross-platform solutions was their "nativeness". Those. Ideally, 100% of users should consider that your cross-platform application is native. And this means that it should look like native, work like native and have all possible integration with the OS. In the beginning, all these points for PhoneGap were unattainable, the capacities of smartphones 10 years ago were not enough for the 60 fps UI render, integration with the OS was minimal. Now there are quite a few applications on Ionic that are difficult to distinguish from native ones, but mimicking a native application is still a taskand not given as such. Let's summarize a little. Writing web applications, or rather hybrid applications on iOS and Android, is possible and convenient. It’s convenient because the UI rendering mechanism lies entirely on the WebView platform, plus there is an already trained layer of programmers who are well versed in the web.However, in hybrid applications, performance and OS integration may be lame.

At the same time as PhoneGap, MonoTouch was launched in 2009, which was later renamed Xamarin.iOS. Also, in the same year, Titanium was released, which in turn also allowed writing iOS applications on javascript. At first, Titanium worked in exactly the same paradigm as PhoneGap - relying on WebView. But then they adopted the Xamarin approach. What is this approach? It can be seen as something in the middle. The approach of Xamarin / Titanium / React.Native is that instead of trying to create / migrate your / existing UI render, the framework simply integrates with the existing, native.

Instead of drawing a form in HTML, Xamarin calls a native UI element for this (UITextField, TextEdit, etc). Indeed, why reinvent the wheel? All the necessary UI elements exist in native SDKs and runtimes, you just need to learn how to communicate with them from your VMs (mono, v8, etc). At the same time, as you already guessed, you can use your favorite C #, JS, TS, F #, Kotlin, etc, and at the same time code that does not directly interact with the UI is 100% cross-platform. You can go even further. The same UITextField and TextEdit are conceptually identical entities, they have quite a few similar properties and interaction interfaces, and therefore, you can create an abstract Entry (hello Xamarin.Forms) and work only with it, for rare ( not very) exception going down to the platform UI element. I don’t mention that if your vm can work with the UI natively, most likely your vm can call any platform APIs. This seems like the perfect option. Native UI, native performance (hi Bridges in React.Native), 100% OS integration. Is this really perfect? Most likely - no, and the problem is that in reality these solutions do not solve the problem of cross-platform development - a single UI. They disguise her. I want to write once, run everywhere. This is far from the best motto for all types of programs and problems, but it fits well with the UI. I want to write UI the same for everyone, regardless of platform. Why can a web developer allow himself to use HTML and CSS to write a site that will then be displayed the same way in Safari on iOS and Chrome on Android, but no native developer?

In fact, programmers have long written a high-performance UI with a common code base for iOS and Android. These programmers are called game developers. Angry Birds was written on Cocos2d-x engine, Cuphead on Unity, and Fortnite on Unreal Engine. If game engines are able to show breathtaking scenes on your phone, then buttons and lists with smooth animation will definitely be able to. So why no one uses them in this vein? The answer is simple and banal, they are not intended for this. When you open the game, it’s absolutely up to the flashlight how much the UI looks like a native one, you almost never need to interact with geolocation, push-buttons, a video camera, etc. While you play, you live a different life in your small world that is rendered through Canvas in your UIViewController / Activity. thereforegame engines have relatively poor integration with the OS , so there is no (or I have not seen) mimicking the native UI top engine.

Subtotals


For an ideal cross-platform framework, we need:

  • Native UI mapping
  • Native UI Performance
  • 100% ability to call any OS API, as if it were a native application

You now think that I will begin to fail under Flutter, but I already hear angry comments: “Where is Qt !? He can do all this! ” Indeed, Qt to one degree or another fits these criteria. Although I strongly doubt the first of them. But the main problem of Qt is not the difficulty of writing a native UI, the main problem is C ++. Then I’m already wiping my face from the spit of labor-encoders on the pluses. Pros is a Swiss knife on anabolic steroids, on the pros you can do everything. But I, as a frontend developer, do not need this all. I need a simple and understandable language that works with UI and I / O. So, to our three points above was added:

  • Easy to learn and quite expressive language
  • Rantime that fits well in the frontend development paradigm

Well, now that we have highlighted some metrics of a good cross-platform tool for developing mobile applications, we can go over each of them and see how it is implemented in Flutter.

Native UI mapping



As we found out earlier, there are two opposite approaches to working with UI in cross-platform frameworks. This is a UI render using WebView or native UI element calls on each platform. Each approach has advantages and disadvantages. But they do not cover the full range of developer needs: look indistinguishable from native UI + native performance. Flutter covers all these needs with a head. The Flutter team spent a certain amount of resources on creating “native” elements in the framework itself. All widgets in Flutter are divided into three large categories:


If you go to the cupertino section, you will see that these widgets are indistinguishable from native iOS elements. As a developer who has been using Flutter for a while, I can confirm that they are indistinguishable. If you use CupertinoDatePicker, for example, when scrolling you will feel exactly the same, nice feedback from the Taptic / Haptic engine on your iPhone as if it were a native element of the native application. I will say more, periodically I open the application of the site realtor.com on my iPhone and until recently I had no idea that it was written in Flutter (or on something not native).

Flutter not only allows you to use "native" widgets for 2 platforms, but also create your own, and it is very easy! The whole paradigm is that everything is widget works. You can create amazingly complex UI elements and animations in a short time. The charms and wisdom of the approach to working with the UI in Flutter have recently been described in this article on Habr, I recommend reading. Because all this works on a single graphics engine that directly renders all of this for each platform (we'll talk about it later), you can be sure that everything will be displayed as you planned.

Another pretty amazing point. Flutter supports platforms starting with iOS 8 and Android API v16. From a UI rendering perspective, Flutter doesn't really matter which APIs are available on a particular platform. He would have the opportunity to work with Canvas and some kind of interaction with the graphics subsystem. And this means that we can draw the latest UI elements from AndroidX, for example, on a phone 8 years old. There certainly is a question of the performance of this approach on the oldest supported platforms, but this is another question.

Native UI Performance



As you can see, Flutter's approach to UI rendering is closer to that of hybrid apps like Ionic. We have a single engine for rendering UI on all platforms, this is Skia Graphics Library. Google bought Skia as a product in 2005 and turned it into an Open Source project. This at least suggests that this is a fairly mature product. Some Skia performance features:

  • Copy-on-write for graphic elements and other data types
  • Using stack memory wherever possible to reduce fragmentation
  • Thread-safety, for better parallelization

I did not find convincing Skia performance tests compared to similar libraries (see Cairo ), but some tests show a 50% performance gain on average, except in some specific situations. Yes, this is not particularly important, because these tests are based on the use of OpenGL on desktops, and ...

Skia can interact with many GPU backends. Since recenttime on iOS, since version 11, Flutter uses Metal as the backend GPU by default. On Android, starting with API 24 - Vulkan. For versions below - OpenGL. All this gives us an obvious gain in productivity. On other "hardware" platforms, as I understand it, Skia / Flutter uses OpenGL, which in principle does not prevent us from writing applications with sufficient graphics performance.

Web stands apart. At the moment, the entire UI render still lays on the Canvas / HTML bundle. Therefore, Skia is in no way involved in this process. Plus, the Dart VM does not interact directly with the DOM. First comes the conversion to js. All this does not have the best effect on productivity and it is directly noticeable to the naked eye. However, work is underway to implement CanvasKitin Flutter, which in turn will allow Skia to be used in browsers via WebGL.

Finally, C # programmers have been using SkiaSharp for a relatively long time - a wrapper over Skia for Mono / .Net x. And the Xamarin community uses this lib to draw custom UI elements and this is a very popular library. If this is not a victory, then I do not know what it is.

100% ability to call any API OS


In Flutter there are 2 principles of interaction with the "outside" world:


Platform Channels allow you to interact with the native runtime / API through a messaging system. From an architectural point of view, this can be seen as follows. Visually, Flutter is just a Canvas, which is stretched to full screen in the only Activity / UIViewController of your native application. This is exactly the same approach that I use game developers (game engines). Those. You can open the iOS / Android project of your application and add any other functionality to Swift / Kotlin / etc. The problem is that the native runtime and the Dart VM will not know anything about each other (in addition to the fact that the native runtime will know that the application has Canvas and something is displayed there). Further, if you, for example, open the MainActivity.kt file of your Android project, you will see something like this:

class MainActivity: FlutterActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    GeneratedPluginRegistrant.registerWith(this)
  }
}

Have you noticed that your Activity inherits from FlutterActivity? This gives us the opportunity to configure the mechanism for sending messages directly to Flutter / DartVM. To do this, we need to override the configureFlutterEngine methodand it will determine the name of the called method and the name of the channel for sending asynchronous messages. All. This makes it possible to write us any native code and call any native API! At the same time, there are already a large number of plugins (packages) that save you from writing native code, you can use only Dart. This is just wonderful! You write the UI separately and once for any platform, use DartVM to work with UI, I / O and just as a computing component, use plugins that implement native features and which cover 99% of all functionality. And if this is not enough, you write natively and communicate through the message mechanism. Story.

The second mechanism is a Foreign function interface or FFI. This is a fairly common term for iterope mechanism with other languages, mainly C. In the .Net world, this mechanism is called P / Invoke, for the JVM it is JNI. In short, this is the ability to interact with libraries written in C / C ++ / etc. At the time of the .Net Framework, for example, there was no software written in C # and the vast majority of software was written in C / C ++, so a mechanism was needed to work with these libraries. The same applies to JVM, Python, you name it. FFI is one way or another used in all cross-platform mobile frameworks. More recently, DartVM has also started supporting FFI for interoperation with C and JavaScript! While this feature is in a beta branch, but is already available for use (at your own peril and risk).

As you can see, Flutter and DartVM cover 100% of the possibilities on native platforms, and even more.

Easy to learn and quite expressive language


I admit honestly, while Dart for me remains not the best language in the world. There is no strict type system, there are no functional buns, such as Pattern Matching or Immutability features (like they will be delivered soon), etc. About the type system, Dart was originally conceived as a “without a typical” language, ala JS, but for normal support for AOT compilation it was nevertheless necessary to bring the type system to a more strict, although not completely, I would say. It still annoys me to work with method signatures, namely with arguments. All these brackets, @requiredfor some reason , are enraging . But dart is a very easy-to-learn language. In syntax, this is a cross between Java and JS for me. Dart forgives a lot, like JS. In general, this is a fairly easy to learn language, I have not experienced any significant problems.

Rantime that fits well in the frontend development paradigm


Now let's talk about Dart VM. In general, Dart VM includes a lot of things, from GC to Profiler and Observatory. Here I want to talk only about GC and conditional runtime. You can familiarize yourself with how the runtime works and what it consists of here . I am not an expert in this field, but for myself, I noted some of the advantages of Dart VM, which I will try to describe. Prior to this, I would like to note that Dart and the corresponding VM were initially developed as a replacement for JS, which, as it were, hints at the focus on frontend development.

Isolates

Dart VM has the Isolate concept. Isolate is a combination of one main thread that runs directly on the Dart code and the isolated heap, where the objects from the Dart code are actually allocated. This is a simplified structure. Isolate also has auxiliary / system threads, there are OS threads that can enter and exit Isolate, etc. The stack is also present in Isolate but you, as a user, do not operate on it. The main thing that needs to be emphasized here is that if you look at one Isolate, then this is a single-thread environment. By default, Flutter uses one default Isolate. Doesn’t resemble anything? Yes this is JS environment. Just like in JS, Dart programmers cannot work with multithreading. Someone might think that this is a mess, simplification and infringement of the rights of real developers, but I think that when working with UI,when you operate with a conditional DOM (and do not draw polygons on the screen), you don’t need to, it’s dangerous to operate with several threads.

Here, of course, I was cunning, if you really want to, then you can use the separately launched Isolate to perform parallel tasks (hello WebWorkers) Here you can see in detail how you can work with additional Isolate in Flutter. In general, Isolates, as the name implies, do not know anything about each other, do not keep links to each other and communicate through a message system.

In addition to the single-thread approach, the fact that a separate heap is allocated for each Isolate without the ability to manipulate the stack of this thread is, in my opinion, a very good approach. If you are writing a server application that manipulates a huge number of lines, for example, and these lines are stored in a heap, where they appear and disappear at a tremendous speed, while fragmenting memory and adding GC jobs, any way of transferring these lines, or at least part, from heaps on the stack will save resources and improve performance. An example is so-so, but you understand me. But when working with UI, where there is possibly a sufficient number of UI elements that can have a short lifetime (for example, animation), but only one client and the amount of processed data is negligible compared to the server application,the ability to directly work with the stack is simply unnecessary. I'm not talking about boxing / unboxing, which could be in this case and which is absolutely pointless. And it should be noted that objects in Dart VM are allocated quite often. Even to output the double amount from the Dart method, the VM separately allocates a piece on the heap. How does the GC handle this load? Let's get a look.

Young Space Scavenger (and Parallel Mark Sweep)

First, like all GCs, the GC in the Dart VM has generations. Also, the GC in the Dart VM can be divided according to the principle of work into 2 components: Young Space Scavenger and Parallel Mark Sweep. I will not dwell on the last principle, this is a fairly popular principle of memory cleaning, which is implemented almost everywhere and does not give Flutter a special advantage. We are interested in the first. The working principle of Young Space Scavenger is well illustrated in the following picture:


It clearly demonstrates the advantages of this approach. Young Space Scavenger works for the newest objects in memory, we can say that for the first / zero generation of objects. Often, and this is characteristic of the Flutter / Dart VM, most new objects have a short life. In a situation where you allocate a lot of objects that do not live long, the memory can be very fragmented. In this case, you will have to pay either memory or processor time to fix the problem (although you should not fix the problem with such methods). Young Space Scavenger solves this problem. If you look at the picture above, then there really is no 6 step, you do not need to clear the first memory chunk, by default we think that this chunk is empty after copying objects to the second. Well, when copying surviving objects into the second chunk,we naturally set them one by one without creating fragmentation. All this allows VM to allocate a lot of new objects at a rather low price.

Idle Time GC

As you understand, the Flutter and Dart VM teams work closely together and the result of this cooperation can be considered the Idle Time GC. As the name implies, this is garbage collection at the moment when nothing happens. In the context of Flutter, at the moment when the application visually does not change anything. There is no animation, scrolling or user interaction. At these moments, Flutter sends messages to the Dart VM that now, in principle, is a good time to start garbage collection. Next, the garbage collector decides whether he should start his work. Of course, garbage collection in this regard occurs for older objects that are managed through the Parallel Mark Sweep, which in itself is a rather expensive process and Idle Time GC is a very useful mechanism in this regard.

There are other things likeSliding Compaction and Compressed Pointers . The first is the memory defragmentation mechanism after running Parallel Mark Sweep. This is also an expensive process and only works if there is Idle Time. The second approach, Compressed Pointers, compresses 64-bit pointers into 32 bits, which saves memory (I think this is much more useful in a server environment than in a mobile one).

Summary


If you read up to this line, then, firstly, congratulations, and secondly, I have to say that I have no experience writing articles, so I don’t quite understand if I managed to get my point across. And the idea is simple, when you write a mobile application with Flutter, it turns out native. And in the form of a bonus you get a very decent application development speed. Hot Reload / Restart is simply an indispensable thing in Frontend development. Can you imagine some typesetter who would need to build / compile the entire project for each browser, for example, with every color change of a button? Of course not. In general, Hot Reload / Restart deserves a separate article. But I was distracted.

My experience with Flutter tells me that this framework will be dominant in the near future. Periodically, I go through interviews for a Flutter developer position and in half the cases, companies that are looking for a Flutter developer actually have a staff of mobile native developers. They just tried Flutter on interior / side projects, were satisfied / delighted and were slowly moving to Flutter. This is a real victory, it seems to me. What can not be said about Xamarin, unfortunately. Quite often, the decision to choose Xamarin is simply due to the fact that the rest of the stack is written in .Net, and this is a slippery slope.

To summarize, I want to say that if you are thinking about which side to approach when developing your new mobile application, look at Flutter.

All Articles