Flutter #2 : Dart, partie 1

Lors de l’article précédent, je vous ai donné un aperçu de ce qu’était Flutter. Cette présentation indiquait que pour développer une application en Flutter il fallait utiliser le langage de programmation Dart.
Dans cet article je vais vous présenter rapidement l’histoire de ce langage ainsi qu’une introduction sur son fonctionnement.
Pour information cet article est destiné à des lecteurs ayant un minimum de connaissance en programmation.

Histoire


Dévoilé en 2011, Dart est un langage créé par Lars Bak et Kasper Lund, des ingénieurs de chez Google, qui, suite à des problèmes rencontrés avec le langage Javascript, décident d’en créer un nouveau qui permettrai de pallier à tout ces problèmes.
C’est donc une équipe de développeurs, habitués à la création de machines virtuelles et aux compilateurs qui s’attellent à cette tâche. En effet, l’équipe a notamment travaillé sur le moteur V8 de Javascript et le compilateur Hotspot de Java.

Lors de sa première version en 2013, Dart s’utilisait sur les navigateurs, avait un typage dynamique et la compilation était en JIT (just-in-time / à la volée).
En 2018 la 2e version de Dart est disponible et le paradigme du langage a complètement été revu, il a désormais un typage statique et voit sa compilation possible aussi en AOT (ahead-of-time/pré-compilé).
Nous verrons en détail plus loin dans l’article ces différents patterns de compilation.

Aujourd’hui, on a donc un langage utilisable sur navigateur, mobile, desktop et serveur avec un typage statique, deux patterns de compilations et qui est en constante évolution. Lors de l’écriture de cette video, Dart est dans sa version 2.7, et parmi les évolutions récentes on peut notamment retenir les extensions de fonctions et les programmes desktop autonomes.

Afin de comprendre comment ce langage fonctionne nous allons passer en revue les spécificités de ce langage orienté objet.

Spécificités

Inférence de types

L’inférence de type permet au compilateur d’associer automatiquement des types aux variables quand ceux-ci ne sont pas déclarés explicitement.

Par exemple, si vous écrivez :

var name = 'Yoda';

Le compilateur va déduire que le type de la variable name d’après sa valeur, en l’occurence un String. Vous pouvez vérifier le type d’une variable avec le mot-clé is ou la fonction runtimeType.

 var name = 'Yoda';  if(name is String) {      print(‘Name est un String’);  }  //autre solution  print(name.runtimeType); // String

Typage

Chaque variable à un type, ce qui permet de limiter les erreurs lors du fonctionnement de l’application et de ne pas avoir à tester le type d’une variable.
Lors de sa première version, Dart avait un typage dynamique et il est aujourd’hui encore possible de programmer de cette façon avec le mot-clé dynamic.
Pour déclarer une variable on aura plusieurs possibilités, vous pouvez préciser le type dès la création de la variable ou bien laisser la valeur définir son type.

String name = ‘Yoda’;
var name = ‘Yoda’;

Attention cependant à l’utilisation du mot-clé var, si on ne lui assigne pas une valeur au même moment de sa création la variable sera dynamique.

var name = ‘Yoda’;// type fixe
var name;// type dynamic
name = ‘Yoda’;

Concernant le typage dynamique, il est possible d’utiliser le mot clé dynamic pour déclarer et assigner une valeur sans typer la variable.

dynamic name = 'Yoda'; print(name.runtimeType); // String   name = 3; print(name.runtimeType); // int 

Machine Virtuelle

Dart fonctionne dans une machine virtuelle plutôt flexible qui permet différents patterns de compilation selon les besoins. Dans la VM de Dart on peut remarquer 3 grands types de composants.

  1. Runtime :
    • Garbage Collector
    • Natives
    • RTTI
    • Snapshots
  2. Patterns de compilation:
    • JIT
    • AOT
    • Interpreter (DBC)
  3. Productivité
    • HotReload
    • Observatory
    • Profiler
    • Debugger
    • VMService

Garbage Collector

Le garbage collector est une combinaison de Young Space Scavenger et Parallelle Mark Sweep.
Le Scavenger s’occupe de gérer les objets récemment alloués en mémoire tandis que le Sweep gère les objets qui survivent plus longtemps dans le Scavenger. C’est un sujet assez long donc si vous voulez avoir plus de détails sur le fonctionnement du G.C. je vous renvois vers cet article.

Natives
Selon la destination de votre compilation vous vous retrouverez avec du Dart Native ou du Dart Web.
Dart Native sera utilisé pour faire fonctionner votre code sur un environnement mobile, desktop et serveur. Dans le cadre du développement d’une application en Flutter la compilation sera différente selon si vous souhaitez compiler une version de développement ou de production.

En mode debug, si on souhaite compiler notre application, la compilation se fera sur un device Android en JIT, sur iOS on retrouvera l’interpreteur DBC.

Pour une release, la compilation se fera sur le host, et l’exécution sur le device. La compilation se fera en AOT et résultera en ARM ou x64.

RTTI
Run-Time Type Information. Cela signifie qu’il est possible de vérifier le type d’une variable pendant le runtime (exécution) du programme.

JIT
La compilation Just-it-time permet de mettre en place une compilation durant l’exécution du programme, dite « A la volée ».

AOT
La compilation AOT , ahead-of-time, compile en natif (x64 ou ARM). Cette méthode de compilation sera utilisée pour la mise en production d’une application.
Cette compilation, comme son nom l’indique, sera effectuée avant le runtime de l’application. Celle-ci sera plus rapide qu’en JIT car tout sera déjà pré-compilé et non compilée à la volée.
C’est ce mode de compilation qui sera utilisé pour les versions de production/release d’une application Flutter aussi bien sur iOS que sur Android.

DBC
Dart Byte Code. La compilation est différente pour iOS , car cet OS interdit la compilation JIT.
Donc pour une compilation dans un environnement de Debug sur iOS, les composants seront donc une interprétation du byte code Dart.

Hot Reload
Le Hot Reload permet de recharger une application en injectant le code modifié depuis la dernière compilation Cela permet un gain de temps non négligeable car cette fonctionnalité est plus rapide qu’une compilation classique, elle est même quasi-instantanée.
Les modifications du code sont envoyé directement dans la machine virtuelle déjà en cours de fonctionnement et de son côté Flutter mettra à jour toute l’interface utilisateur.

Mono-Thread et Asynchrone

Dart est un langage mono thread qui offre la possibilité de faire de l’asynchrone. Pour comprendre comment tout ça marche on va jeter un oeil du côté des Future, des isolates et de l’event loop.

Quand vous aurez une application lancée, elle tournera dans ce qu’on appel un isolate. Un isolate est un espace qui possède sa propre mémoire et une boucle d’évènement qui sont exécutées par ordre d’arrivée.

Imaginons que dans cet isolate, on cherche à effectuer une tâche, par exemple une requête à un webservice ou une écriture sur une base de donnée.
Ce genre d’action n’est pas immédiate, il y aura toujours une latence avant que celle-ci soit terminée. Pour l’exemple du webservice, le serveur appelé peut mettre quelques millisecondes à répondre. Il faut donc prendre en compte le fait qu’on n’aura pas une réponse immédiate.
Pour prendre en compte cette problématique en Dart, on va faire de l’asynchrone à l’aide des mot-clés Future et Async/Await.

Concurrente : La machine virtuelle de Dart fonctionne avec un Isolate. Lors de la programmation de votre application, si vous avez besoin de mettre en place des opérations lourdes (lecture de base de donnée par exemple) il sera judicieux de créer un autre Isolate spécifique à cette operation afin de limiter l’occupation de l’isolate déjà présent dans la machine virtuelle.

Mixin : Une classe qui peut être incluse dans une autre pour former un objet sans pour autant en être le parent. Il est possible de limité l’usage d’une mixin à certaines classes.

Streams : Dart permet de faire de la programmation réactive fonctionnelle. Il existe même une librairie RxDart !

Paradigme multiple : Dart peut être utilisé aussi bien dans une optique Orienté Objet, fonctionnel etc.