Null-safety en Flutter

Salut, je suis Yves AMANGOUA. Je suis ravi de te retrouver pour aborder une notion aussi importante qu’indispensable pour programmer en Flutter et même dans pratiquement tous les langages qui intègrent la notion de Programmation Orienté Objet (POO), le NULL-SAFETY.

La notion de null-safety est apparue dans Dart à partir de sa version 2.x. Selon wikipédia, la sécurité vide ou Null-safety est une garantie dans un langage de programmation orienté objet qu’aucune référence d’objet n’aura de valeurs nulles ou sera non avenues. En d’autres termes, c’est un moyen de protéger vos applications contre le risque de crash dû à des exceptions inattendues, quand vous utilisez des variables ayant une valeur nulle dans un endroit de votre code, que vous n’avez pas prévu. Dans la suite nous parlerons, dans cet article, de null-safety appliqué au langage Dart.

Null-safety en Dart

Dart est un langage orienté objet. Dans les langages orientés objet, l’accès aux objets se fait par des références. Un appel typique est de la forme :

Dart
x.f(a, ...)

Null-safety prévient les erreurs résultantes de l’utilisation involontaire d’une variable ayant une valeur null à un endroit précis de votre code. Par exemple, en appelant une méthode qui doit retourner en principe un entier, mais retourne au moment de l’appel la valeur null. Alors, votre application lèvera une exception ou crashera. En effet, sans la protection null-safety, il peut être assez difficile de débogguer votre code et de repérer toutes les références potentiellement nulles de variables ou méthodes là où elles ne devraient pas être nulles.

Quand la protection null-safety est activée, toutes les variables déclarées sont « non-nullable » par défaut. Ainsi à la déclaration, vous devez indiquer si une variable peut contenir ou non la valeur « null » durant toute sa ligne de vie dans le programme, en ajoutant le symbole ? à son type.

Exemple de déclaration

  • Variables « nullable »
Dart
//Syntaxe
type? var_name;

//Variables nullable, pouvant avoir une valeur null dans le code
int? var1;

String? var2 = "youskil";

double? var3 = 2.5;
  • Variables « non-nullable »
Dart
//Syntaxe
type var_name;

//Variables ne pouvant pas avoir de valeur null dans le code
var var4 = 10; //le type de cette variable est int et non int?

String nom = getNom(); //le type de cette variable est String et non String?

final moyenne = 12.1; //le type de cette variable est double et non double?

Comme nous venons de le voir avec les exemples ci-dessus, toutes variables déclarées sans le symbole ? à leurs types, ne peuvent en aucun cas recevoir la valeur null pendant toute leur durée de vie dans le programme. Cependant en précisant le symbole ? auprès du type de la variable, cette variable pourra recevoir une valeur null dans le code.

Principes de null-safety

La prise en charge de null-safety en Dart est basée sur les trois principes de conception de base suivants :

  • Toute variable est non-null par défaut : à moins que vous ne disiez explicitement à Dart qu’une variable peut être nulle, elle est considérée comme non nulle. Cette valeur par défaut a été choisie après que des recherches ont révélé que non nul était de loin le choix le plus courant dans les API.
  • Adoptable progressivement : vous choisissez quoi migrer vers la sécurité nulle, et quand. Vous pouvez migrer de manière incrémentielle, en mélangeant du code null-safe et non-null-safe dans le même projet. Google fournit des outils pour vous aider avec la migration.
  • La sécurité nulle de Dart est solide, ce qui permet des optimisations du compilateur. Si le système de type détermine que quelque chose n’est pas nul, alors cette chose ne peut jamais être nulle. Une fois que vous avez migré l’ensemble de votre projet et ses dépendances vers la sécurité nulle, vous récoltez tous les avantages de la solidité, non seulement moins de bogues, mais des fichiers binaires plus petits et une exécution plus rapide.

Autres exemples

  • Cas d’une classe

Comme déjà énoncé un peu plus haut, toute variable est par défaut non-nullable, c’est à dire n’est pas apte à recevoir une valeur nulle. C’est aussi valable pour les attributs de classe. Un attribut non-nullable, doit obligatoirement être initialisé par le constructeur de la classe.

Dart
class Eleve{
  //Attributs de la classe non-nulls
  String nom;
  String prenoms;
  int age;
  
  //Constructeur de la classe
  Eleve(this.nom, this.prenoms, this.age);
}

Quant-aux attributs nullables, vous n’êtes pas obligés de les initialiser avec le constructeur.

Dart
class Eleve{
  //Attributs de la classe nullables
  String? nom;
  String? prenoms;
  int? age;
  
  //Constructeur de la classe
  Eleve({this.nom, this.prenoms, this.age});
}
  • Cas des méthodes ou fonctions : null-safety s’applique aussi aux méthodes ou fonctions. Le principe est également simple. Une méthode est constituée (la signature) de :
    • Un type de retour,
    • du nom de la méthode et
    • de variables qui constituent les paramètres de la méthode.
Dart
type nom_methode(type param1, type param2, ...)

Ici, null-safety va s’appliquer au type de retour et aux paramètres des méthodes. Voici un cas concret…

Dart
class Eleve{
  ...
  
  double calculerMoyenne(double note1, double note2){
    return (note1 + note2)/2;
  }
  
  ...
}

L’exemple ci-dessus dit que la méthode calculerMoyenne retournera toujours un double. Ses paramètres, c-à-d note1 et note2, ne seront jamais nuls. Voyons maintenant un cas avec des variables nullables.

Dart
class Eleve{
  ...
  
  double? calculerMoyenne(double? note1, double? note2){
    if(note1 != null && note2 != null){
      return (note1 + note2)/2;
    }
    return null;
  }
  
  ...
}

Dans cet exemple, on remarque que le type de retour, la note1 et note2 peuvent être nuls. Par conséquent, il faut systématiquement vérifier que les paramètres ne sont pas nuls avant de pouvoir effectuer le calcul. Sinon, le compilateur aurait empêché la compilation. On remarque aussi que notre méthode calculerMoyenne peut retourner une valeur nulle ou un double c-à-d un double?. Ce qui est tout à fait logique.

Ainsi dès l’écriture du code, vous pouvez savoir exactement les endroits où vous pouvez avoir des valeurs nulles et même décider des endroits dans lesquelles vous pouvez accepter des valeurs nulles. C’est pas génial ça ? ?

Voyons maintenant comment utiliser les variables nullables dans notre code Dart.

Utilisation des variables

  • Cas des variables non-nullables

L’utilisation de variables non-nullables se fait tout à fait normalement, comme vous avez l’habitude de le faire en Dart ou dans tous les autres langages de programmation.

Dart
class Eleve{
  String nom;
  String prenoms;
  int age;
  
  Eleve(this.nom, this.prenoms, this.age);
}

//Une fonction qui met un text en majuscule et le renvoie.
String majuscule(String text)=> text.toUpperCase();


void main(){
  Eleve e1 = Eleve("John", "Doe", 12);
  String maj = majuscule(e1.nom); //aucune erreur
  print(maj);
  //Sortie
  JOHN
}

Reprenons le même exemple, mais avec des variables nullables cette fois.

  • Cas de variables nullables
Dart
class Eleve{
  String? nom;
  String? prenoms;
  int? age;
  
  Eleve({this.nom, this.prenoms, this.age});
}

//Une fonction qui met un text en majuscule et le renvoie.
String majuscule(String text)=> text.toUpperCase();


void main(){
  Eleve e1 = Eleve(prenoms: "Doe");
  String maj = majuscule(e1.nom); //erreur de compilation 
}

Dans cet exemple, vous aurez une erreur à la compilation. C’est tout à fait normal. En effet, à aucun moment, l’attribut « nom » n’a été initialisé. Il est donc impossible de passer une variable nulle en paramètre à la méthode majuscule qui attend un String. Cela aurait été possible si seulement la méthode majuscule attendait un String?.

Voyons ensemble un autre cas d’utilisation de ce type de variable.

L’opérateur ‘!’

Dart
class Eleve{
  String? nom;
  String? prenoms;
  int? age;
  
  Eleve({this.nom, this.prenoms, this.age});
}

//Une fonction qui met un text en majuscule et le renvoie.
String majuscule(String text)=> text.toUpperCase();


void main(){
  Eleve e1 = Eleve(prenoms: "Doe");
  e1.nom = "John";
  String maj = majuscule(e1.nom!); //aucune erreur de compilation 
  print(maj);
  //Sortie
  JOHN
}

Dans ce cas, nous avons assigné la valeur « John » à l’attribut nom. Ensuite, si nous avons besoin de l’utiliser dans notre méthode majuscule, on doit préciser que bien que l’attribut soit nullable, nous somme certain qu’à cet endroit du code l’attribut nom n’est pas nul. Alors on utilise, pour cela, le symbole ! à la fin du nom de l’attribut.

NB : Si nous n’avions pas assigné de valeur au nom et que nous avions marqué que le nom n’est pas nul comme le code précédent le montre, Le programme aurait compilé. Cependant, on aurait eu une erreur à l’exécution de cette partie du code. Exemple :

Dart
...

void main(){
  Eleve e1 = Eleve(prenoms: "Doe");
  //  e1.nom = "John";
  String maj = majuscule(e1.nom!); //Erreur à l'exécution
}

Le mot clé « late »

Le mot clé late est utilisé pour aussi déclarer une variable. Il se place avant le type de la variable déclarée.

Dart
class Eleve{
  late String nom;
  late String prenoms;
  late int age;
  
  Eleve({this.nom, this.prenoms, this.age});
}

Il est utilisé pour dire qu’une variable n’est pas nullable, mais au moment de la déclaration la variable n’a aucune valeur. Du coup, on dit au compilateur qu’on est sûr qu’au moment où l’on voudra lire le contenu la variable, elle sera déjà initialisée. Exemple :

Dart
class Eleve{
  late String nom;
  late String prenoms;
  late int age;
  
  Eleve({this.nom, this.prenoms, this.age});
}

void main(){
  Eleve e1 = Eleve(nom : "John");
  print(e1.nom); //Sortie John
  print(e1.prenom); //Erreur lors de l'exécution
}

Conclusion

Ça y est vous savez l’essentiel sur la notion de null-safety. Il faut dire qu’écrire une application est une tâche assez complexe et fastidieuse. Cependant plusieurs outils et concepts comme null-safety sont mis en place pour nous aider dans cette tâche. Ainsi, ils contribuent à protéger nos applications de crash pour les rendre de plus en plus performantes. J’espère que vous avez pu trouver plaisir à lire cet article. Vous pouvez aller plus en profondeur, si vous le souhaitez, en consultant la documentation officielle de Dart. Vous pouvez nous laisser un commentaire pour toutes incompréhensions ou difficultés que vous avez rencontré dans cette article. Je me ferai un grand plaisir de vous répondre. En attendant, à très bientôt pour un nouvel article. Peace ! ?

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *