Why Flutter is built all around Dart?

Introduction

Numerous language specialists accept that the normal dialect an individual talks influences how they think. Does the same concept apply to computer dialects? 
Software engineers who are working on different sorts of programming languages regularly come up with profoundly diverse solutions to issues. As a more extraordinary case, computer researchers dispensed with the goto statement to encourage more organized programs. This is the same way the Flutter team has thought of when they picked up Dart. After evaluating more than 12 programming languages, they chose Dart because it matched with the way they planned to build the user interfaces (UI).

 

The code journey from compilation to execution

Historically, computer languages have been divided into two groups:

  • Static languages where variables are well defined and statically typed at compile time (e.g., Java(Without Reflection), C or C++ ).
  • Dynamic languages where the type of a variable can change at run time (e.g., Ruby, PHP or JavaScript).

Static languages were typically compiled to produce native machine code (or assembly code) programs for the target machine, which at run time were executed directly by the hardware. Dynamic languages were executed by a middleware (Usually an interpreter) without producing machine language code.

Undoubtedly, things eventually became much more complicated. The concept of a virtual machine (VM) became popular, which is really just an advanced interpreter that mimics a hardware machine in software. A virtual machine makes it easier to port a language to new hardware platforms. In this case, the input language of a VM is often an intermediate language. For example, a programming language (such as Java) is compiled into a intermediate language (bytecode) and then executed on a Java Virtual Machine known as JVM .

In addition, there are now just-in-time (JIT) compilers. A JIT compiler runs during the execution of the program, compiling on the fly. The original compilers that execute during the build process of the program (before runtime) are now called ahead-of-time (AOT) compilers.

Generally, only static languages are compliant with AOT compilation into native machine code because machine languages typically need to know the type of data, and in dynamic languages the type is not fixed ahead of time. Consequently, dynamic languages are typically interpreted or JIT compiled.

When AOT compilation is done during development, it invariably results in much slower development cycles (the time between making a change to a program and being able to execute the program to see the result of the change). But AOT compilation results in programs that can execute more predictably and without pausing for analysis and compilation at runtime. AOT compiled programs also start executing faster (because they have already been compiled).

Conversely, JIT compilation provides much faster development cycles, but can result in slower or jerkier execution. In particular, JIT compilers have slower startup times, because when the program starts running the JIT compiler has to do analysis and compilation before the code can be executed. Studies have shown that many people will abandon an app if it takes more than a few seconds to start executing.

So after all, wouldn’t it be awesome to combine the advantages of both AOT and JIT compilation?

Compiling and executing Dart

Before working on Dart, the Dart team members had done groundbreaking work on advanced compilers and virtual machines, both for dynamic languages (like the V8 engine for JavaScript) and for static languages (like the Hotspot compiler for Java). They used this experience to make Dart unusually flexible in how it can be compiled and executed.

 

Dart is one of very few languages that is well suited to being compiled both AOT and JIT. Supporting both kinds of compilation provides significant advantages to Dart and (especially) Flutter.

JIT compilation is used during development, using a compiler that is especially fast. Then, when an app is ready for release, it is compiled AOT. Consequently, with the help of advanced tooling and compilers, Dart can deliver the best of both worlds: extremely fast development cycles, and fast execution and startup times.

Dart can also be compiled into JavaScript so it can be executed by browsers. This allows code reuse between mobile apps and web apps. Dart can also be used on a server either by being compiled to native code, or by compiling to JavaScript and using it with node.js.

Finally, Dart also provides a standalone VM that uses the Dart language itself as its intermediate language (essentially acting like an interpreter).

Dart can be efficiently compiled AOT or JIT, interpreted, or transpiled into other languages. Not only is Dart compilation and execution unusually flexible, it is especially fast.

Render & Animate jank-less

By running your apps on 60 fps, Dart doesn’t only guarantee a fast app but it secures a smooth app which is even better. Even a super fast animation will look bad if it is jerky. However, preventing jank can be difficult because there are so many different causes. Dart has a number of features to avoid many of the common things that cause jank.

Of course, (like any language) it is still possible to write a janky app in Flutter; Dart helps by being more predictable and giving the developer more control over the smoothness of their app, making it easier to provide the best user experience possible, bar none.

 

AOT burns the “bridge”

We’ve already discussed one feature that helps keep things smooth, and that is Dart’s ability to be AOT compiled to native machine code. Pre-compiled AOT code is more predictable than JIT because there are no pauses during runtime to perform JIT analysis or compilation.

 

However, there is an even bigger advantage to AOT compiled code and that is avoiding the “JavaScript bridge”. When dynamic languages (like JavaScript) need to interoperate with native code on the platform, they have to communicate over a bridge, which causes context switches that have to save a particularly large amount of state (potentially to secondary storage). These context switches are a big burden because they not only slow things down, they can cause serious jank.

Note: even compiled code may need an interface to talk to platform code, and this can also be called a bridge, but it is typically orders of magnitude faster than the bridge required by a dynamic language. In addition, because Dart allows things like widgets to be moved into the app, the need to go over a bridge is reduced.

Concurrency and Shared Resources

 Most computer languages that support multiple concurrent execution threads (including Java, Kotlin, Objective-C, and Swift) use preemption to switch between threads. Each thread is allocated a “slice” of time to execute, and if it exceeds its allocated time the thread is preempted using a context switch. However, if the preemption occurs when a resource that is shared between threads (like memory) is being updated, then this causes a race condition.

 

Race conditions are a double whammy because they can cause serious bugs, including crashing your app and causing data to be lost, and they are particularly difficult to find and fix because they depend on the relative timing of independent threads. It is all too common for race conditions to stop manifesting themselves when you run the app in a debugger.

The typical way to fix a race condition is to protect the shared resource using a lock that prevents other threads from executing, but locks themselves can cause jank, or even more serious problems including deadlock and starvation.

Dart took a different approach to this problem. Threads in Dart, called isolates, do not share memory, which avoids the need for most locks. Isolates communicate by passing messages over channels, which is similar to web workers in JavaScript.

Dart, like JavaScript, is single threaded, which means it does not allow preemption at all. Instead, threads explicitly yield (using async/await, Futures, or Streams). This gives the developer more control over execution. Single threading helps the developer ensure that critical functions like animations and transitions are executed to completion, without preemption. This is often a big advantage not just for user interfaces, but for other client-server code.

Not to mention that if the developer forgets to yield control, this can delay other code from executing. However, forgetting to yield is usually much easier to find and fix than forgetting to lock because race conditions are difficult to find.

Memory Allocation and Garbage Collection

Another serious cause of jank is garbage collection. Indeed, this is just a special case of accessing a shared resource (memory), which in many languages requires the use of locks. But locks might stop the entire app from running while free memory is collected. However, Dart can perform garbage collection almost all of the time without locks.
 

Dart uses an advanced generational garbage collection and allocation scheme, which is particularly fast for allocating many short-lived objects (perfect for reactive user interfaces like Flutter that rebuild the immutable view tree for every frame). Dart can allocate an object with a single pointer bump (no lock required). Once again, this results in smooth scrolling and animation, without jank.

Conclusion

This is an exciting time for Dart. Dart is super fast and super suitable for rendering-crunching apps. Dart has a super easy learning curve which made people loves it and contribute to the community with more than 30,000 packages on Flutter official Pub Dev. Not to mention the new features in Dart 2 that shall make it an even more valuable addition to your arsenal of tools.

 

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
Scroll to Top