Dart, notions avancées
Introduction
Après avoir exposé les fonctionnalités de base du langage Dart, il est utile à présent d’en découvrir encore davantage pour être parfaitement à l’aise avec le sujet.
Les concepts et notions détaillés dans ce chapitre sont suffisants dans le cadre de cet ouvrage afin de découvrir Flutter et pour être suffisamment à l’aise. Malgré tout, il restera encore beaucoup de choses à découvrir concernant Dart. Il n’est pas envisageable de décrire l’ensemble des possibilités de ce langage sur deux chapitres. Il faut comprendre qu’il s’agit d’un langage à part entière et qu’un ouvrage entier pourrait lui être consacré.
Toutefois, rien n’empêche de découvrir les bases (déjà conséquentes) au travers de nombreux thèmes. Le premier abordé sera les fonctions. Le mot est volontairement un peu généraliste. Par la suite, un aspect volumineux du langage sera vu à travers la programmation orientée objet (POO). Ici, les classes et tout ce dont elles sont composées seront étudiées. Leurs interactions (associations, héritages…) permettront de mieux comprendre l’utilité de cette organisation. C’est aussi le moment idéal pour comprendre comment...
Fonctions
1. Introduction
Il est temps maintenant d’aborder une première notion indispensable : il s’agit des fonctions. Elles vont permettre un découpage précis du code et le regroupement de fonctionnalités. Il existe plusieurs types de fonctions partant de la fonction simple, passant par la fonction avec paramètre ou encore la fonction avec retour et même la fonction anonyme.
2. Fonction simple
Une fonction, dans sa forme la plus simple, n’attend pas de paramètre et ne renvoie rien. Cette fonction est de type void. Dans le jargon informatique, elle se nomme procédure. Dans le code présenté lors du chapitre précédent, elle a été rencontrée au travers notamment d’un élément crucial, main, le point d’entrée du programme. Pour rappel, sa syntaxe est la suivante :
void main() {
//Point d'entrée du programme
}
Voici un autre exemple. Si le but du programme est d’afficher le traditionnel « Hello World ! », il est possible d’envisager de conserver main et d’écrire une fonction qui sera appelée et dont le rôle sera d’afficher cette phrase. Voilà ce que cela peut donner :
void main() {
//Point d'entrée du programme
afficher();
}
void afficher() {
print("Hello World !");
}
Le programme ci-dessus s’exécute, car il contient une procédure main. La première et unique action effectuée est d’appeler une fonction se nommant afficher(). Celle-ci est écrite juste en dessous. Elle est de type void puisqu’elle ne renvoie rien. Son seul but est d’afficher dans la console le contenu de la fonction print(). Le code est bien scindé et commence à avoir une structure plus ordonnée.
3. Fonctions avec retour
Une fonction avec retour se comporte globalement de la même manière que la fonction simple hormis qu’une réponse va être renvoyée.
Ces fonctions sont utilisées dans un grand nombre de situations comme pour la réalisation d’un calcul, la vérification...
Programmation orientée objet (POO)
1. Introduction
La programmation orientée objet, couramment appelée POO, est un paradigme de programmation. L’idée principale est de faire en sorte que la représentation que l’être humain a du monde qui l’entoure puisse être traduite en code informatique. Pour cela, il convient de représenter une problématique au travers des acteurs, éléments ou objets en jeu.
Si le but d’un logiciel est de gérer des clients et des fournisseurs, le plus simple va être d’obtenir une représentation de chacun d’eux sous forme de code exploitable. Pour commencer, il faudra créer des classes qui seront en quelque sorte le moule de chaque élément. À l’intérieur, des caractéristiques intrinsèques à chaque objet seront décrites, il s’agit des attributs. Ces objets disposeront également de fonctionnalités (méthodes). Par la suite, elles seront capables de travailler ensemble soit par le biais d’un héritage ou d’une association.
2. Classe
La classe peut être considérée comme un moule qui va servir à créer des objets appelés instances. Pour créer une classe, il suffit d’ouvrir un nouveau fichier Dart. Une fois ouvert, il suffit d’écrire le mot-clé class puis de nommer celle-ci et enfin d’ouvrir des accolades :
class Personne {
}
Une classe peut être créée un peu n’importe où. À la suite d’une fonction main par exemple. Il s’agit alors d’une classe interne. Dans le cadre d’une bonne pratique, il est conseillé de créer des fichiers indépendants qui ne contiendront chacun qu’une classe. Pour les utiliser dans un autre fichier, celui contenant la procédure main par exemple, il faudra alors réaliser un import :
import 'Personne.dart';
void main() {
}
3. Attributs
Dans une classe, les attributs pourraient être considérés comme les caractéristiques de l’objet. Une personne possède un nom, un prénom, une date de naissance… Une voiture, elle, a une couleur, une marque, un modèle…
À l’intérieur...
Généricité
La généricité est couramment utilisée pour rendre le travail plus concis et polyvalent. Elle offre la possibilité à une classe qui s’appuie sur une autre dans le cadre d’une association de faire en sorte de ne plus se limiter à celle-ci, mais de pouvoir accueillir n’importe quelle classe à la place.
La généricité a déjà été abordée sans le savoir durant le chapitre précédent au travers des listes. Les listes peuvent travailler avec tous types d’objets. La généricité se reconnaît par sa syntaxe singulière où le type attendu s’écrit entre chevrons.
List<int> listeDeNombres;
Il existe bien sûr quelques classes génériques déjà présentes dans le SDK comme les listes. Mais l’intérêt principal est de pouvoir créer nos propres classes usant de cet effet.
Dans l’exemple suivi dans ce chapitre, une classe Adresse avait été créée et associée à Personne. Cependant, en fonction des pays de résidence, les adresses varient. Si les personnes utilisant l’application sont françaises ou américaines, il faudra être capable de gérer les deux sortes d’adresses. La différence se situe principalement sur le fait qu’en France un code postal (de cinq caractères) est utilisé, alors qu’aux États-Unis il s’agit d’un code ZIP (sur cinq ou neuf caractères) additionné à un État.
Pour commencer, il faut modifier Adresse puisque cette classe ne sera plus instanciée. Elle va donc devenir abstract puis servira de classe mère à deux nouvelles classes : AdresseFR et AdresseUS. L’attribut codePostal va aussi disparaître.
Voici le code modifié de Adresse :
abstract class Adresse {
int _numeroDeVoie;
String _nomDeLaVoie;
String _nomDeLaCommune;
Adresse(int numeroDeVoie, String nomDeLaVoie,
String nomDeLaCommune)...
Exception
Les exceptions sont des erreurs. Elles peuvent se lever à divers moments pendant l’exécution du programme comme au milieu d’un traitement de type algorithmique ou bien à la suite d’une action de l’utilisateur. Elles indiquent que quelque chose s’est mal déroulé.
Elles peuvent se classer en deux catégories :
-
Les exceptions dites « systèmes ». Elles sont dues, la majorité du temps, à une action courante et potentiellement dangereuse et pour laquelle une exception bien particulière a été prévue.
-
Les autres sont plutôt liées à des problématiques que nous nous fixons nous-mêmes, comme des contraintes métier. Elles sont volontairement levées quand une action non conforme a lieu.
1. Exceptions systèmes
Les exceptions systèmes sont courantes. Elles répondent aux problématiques les plus fréquentes. Dart comprend six grands types d’exceptions :
-
DefferredLoadException : levée quand une bibliothèque différée ne se charge pas.
-
FormatException : levée lorsqu’une chaîne de caractères ou d’autres données ne possèdent pas le format attendu et ne peuvent pas être converties ou traitées.
-
IntegerDivisionByZeroException : levée quand une division par zéro est tentée sur un nombre.
-
IOException : levée quand un problème survient lors des opérations de types IO (Input/Output).
-
IsolateSpawnException : levée quand un isolate ne peut pas être créé.
-
TimeOutException : levée lorsqu’un délai d’expiration planifié est atteint en attendant un résultat asynchrone.
Toutes ces exceptions implémentent la classe Exception qui est plus générique. Il faut également préciser que d’autres exceptions encore plus spécifiques existent. Ces dernières implémentent au moins une des six exceptions citées plus haut.
Voici un exemple afin de mettre cela en pratique. Le code suivant est incorrect :
void main() {
String nom = null;
nom.toUpperCase();
}
Il n’est pas possible d’appliquer toUpperCase()...
Traitement asynchrone
L’asynchronicité est la possibilité pour une tâche d’être réalisée en parallèle d’une autre dans un laps de temps qui diffère. Par exemple, obtenir de l’information depuis une base de données peut s’avérer long. Dans cette situation, il faut à tout prix éviter de bloquer l’application, le reste du programme doit continuer à fonctionner pendant cette recherche. La sollicitation de la base sera donc asynchrone.
Dart dispose de deux mots-clés pour l’asynchronicité : async et await.
Conjointement, ils sont souvent associés à la classe Future<T>. Cette classe est utilisée pour représenter une réussite comme le résultat d’un calcul, ou la récupération de données (ou un échec), qui sera disponible à un moment précis dans le futur.
Il existe aussi une autre classe, Stream, qui permet de travailler sur le sujet. Cette notion sera abordée dans le chapitre Les États.
Pour illustrer les propos évoqués plus haut, mettons en place ce code :
Future<void> main() async {
print("1- J'arrive en premier");
print(await retour1());
print("5- J'arrive en dernier");
}
Future<String> retour1() { ...