Fondamentaux de Dart
Introduction au développement Dart
Section titled “Introduction au développement Dart”Dart est un langage de programmation open-source développé par Google et présenté au public en 2011. Initialement imaginé comme un remplacement de JavaScript pour le développement web, cette ambition a été abandonnée, mais le langage n’a cessé d’évoluer et est maintenant utilisé en interne chez Google sur des projets critiques.
Dart est un langage qui devrait paraître familier aux développeurs connaissant Java, C#, JavaScript, Kotlin ou Swift.
Hello Dart
Section titled “Hello Dart”Un programme Dart nécessite une fonction main, son point d’entrée unique.
void main() { print('Hello, Dart!');}Les bases
Section titled “Les bases”Variable (var)
Section titled “Variable (var)”Déclaration d’une valeur variable :
var firstname = "Pierre";firstname = "Jean-Pierre"; // OK
var total = 0.0;total += 3.0;total -= 2.5;Types intégrés (Built-in types)
Section titled “Types intégrés (Built-in types)”Dart propose plusieurs types intégrés :
Stringnum,int,doubleboolListMapSetRecordRunes,SymbolNull
Sound null safety
Section titled “Sound null safety”Dart (version 2.12 et supérieure) est “null safe”. Par défaut, les variables typées ne peuvent être null.
Pour signaler au compilateur qu’une variable ou qu’un paramètre peut être null, il est nécessaire de l’expliciter grâce au ? :
String? name; // Peut être nullString title = "Hello"; // Ne peut pas être nullLes valeurs dynamic sont “nullables” par défaut.
String handling
Section titled “String handling”var title = "Introduction à Dart";print('${title.split(' ')}');Typage statique explicite
Section titled “Typage statique explicite”Il est possible d’expliciter le type des variables :
String name = 'Voyager II';int maxLength = 9;double price = 9.99;List<String> planets = ['Jupiter', 'Saturn', 'Uranus', 'Neptune'];Map<String, dynamic> imageJupiter = { 'tags': ['jupiter'], 'url': '//path/to/jupiter.jpg'};final et const
Section titled “final et const”Les variables final ne peuvent être déclarées qu’une seule fois, mais leur contenu est mutable. Grâce à l’inférence de type, le typage explicite est optionnel.
final total = price1 + price2;total = 30; // ⚠️ Erreur compilationtotal += 10; // ⚠️ Erreur compilationLes variables const sont initialisées pendant la compilation et sont entièrement immuables.
const defaultErrorMessage = 'Ooops...:|';const ok = 200;const notFound = 404;Structures de contrôle
Section titled “Structures de contrôle”Conditions :
if (age <= 2) { return 0;} else if (age > 2 && age < 13) { return 0.5;} else { return 1;}Boucles
Section titled “Boucles”// For-in loopfor (final user in group) { print(user);}
// For loopfor (int month = 1; month <= 12; month++) { print(month);}
// While loopwhile (year < 2016) { year += 1;}Switch
Section titled “Switch”Switch classique :
switch(route) { case '/login': screen = LoginScreen(); break; case '/': screen = HomeScreen(); break; case '/404': default: screen = NotFoundScreen();}Switch expression (Dart 3.0) :
final view = switch(result) { Result.pending => ProgressView(), Result.error => ErrorView(), Result.data => UserList(),};Collections
Section titled “Collections”final cityList = ['Paris', 'Londres', 'Rome'];cityList.add('Amsterdam');cityList.addAll(['Madrid', 'Barcelone']);cityList.removeLast();cityList.add('Rome');
final uppercaseCities = cityList .map((city) => city.toUpperCase()) .toList();print(uppercaseCities);Collection d’éléments uniques :
final citySet = {'Paris', 'Londres', 'Rome'};citySet.add('Rome'); // N'ajoute pas de doublonprint('Cities $citySet');final city = { 'name': 'Paris', 'country': 'France', 'pictures': ['paris1.jpg', 'paris2.jpg'],};
enum Status { pending, error, done }
Map<Status, Color> colorMap = { Status.pending: Colors.grey, Status.error: Colors.red, Status.done: Colors.green,};
void main() { print(city['country']); print(colorMap[Status.error]);}Records
Section titled “Records”Depuis Dart 3.0 :
void main() { // Records positionnels final position = getPoint(); print('position X : ${position.$1}, Y : ${position.$2}, Z : ${position.$3}');
// Destructuring final (x, y, z) = getPoint(); print('position X : $x, Y : $y, Z : $z');
// Named records final coordinates = getPosition(); print('longitude ${coordinates.longitude} latitude ${coordinates.latitude}');}
(double, double, double) getPoint() { final x = computeX(); final y = computeY(); final z = computeZ(); return (x, y, z);}
({double longitude, double latitude}) getPosition() { final longitude = computeLongitude(); final latitude = computeLatitude(); return (latitude: latitude, longitude: longitude);}Fonctions
Section titled “Fonctions”Les fonctions Dart doivent être typées, pour les paramètres et la valeur retournée. Les fonctions sans retour sont typées void.
void main() { final values = [1, 2, 5, 6, 7]; final total = sum(values); print('Somme : $total\nMoyenne ${average(values)}\n' 'Moyenne arrondie ${roundAverage(values)}');}
int sum(List<int> values) { final result = values.reduce((value, item) => value + item); return result;}
double average(List<int> values) => sum(values) / values.length;
int roundAverage(List<int> values) => sum(values) ~/ values.length;Fonction one-liner
Section titled “Fonction one-liner”double average(List<int> values) => sum(values) / values.length;Paramètres facultatifs
Section titled “Paramètres facultatifs”String hello([String? name]) { return 'Hello ${name ?? 'Dart'}';}
print(hello('Bob'));Paramètres facultatifs nommés
Section titled “Paramètres facultatifs nommés”String hello({String? name}) { return 'Hello ${name ?? 'World'}';}
print(hello(name: 'Bob'));Paramètres nommés requis
Section titled “Paramètres nommés requis”Les paramètres nommés apportent beaucoup de lisibilité, mais sont optionnels, sauf s’ils sont précédés d’un required.
String hello({required String name}) { return 'Hello $name';}
print(hello(name: 'Bob'));Programmation Orientée Objet (POO)
Section titled “Programmation Orientée Objet (POO)”Classes
Section titled “Classes”Dart propose une POO relativement classique avec des classes pourvues de constructeurs. Il est possible de déclarer des constructeurs nommés, permettant de créer des “configurations” d’instanciation particulières.
void main() { final counter = ValueNotifier(0); counter.addListener(() => print('listener 1 : ${counter.value}')); counter.addListener(() => print('listener 2 : ${counter.value}')); counter.value = 1;}
typedef Listener = void Function();
abstract class Notifier { final _listeners = <Listener>{};
void addListener(Listener listener) { _listeners.add(listener); }
void removeListener(Listener listener) { _listeners.remove(listener); }
void removeAllListeners() { _listeners.clear(); }
void notifyListeners() { for (final listener in _listeners) { listener.call(); } }}
class ValueNotifier<T> extends Notifier { T _value;
T get value => _value;
set value(T newValue) { _value = newValue; notifyListeners(); }
ValueNotifier(T value) : _value = value;}Constructeurs
Section titled “Constructeurs”class Response { final int statusCode; final String body;
Response({required this.statusCode, required this.body});
Response.ok({required this.body}) : statusCode = 200;
Response.notFound({required this.body}) : statusCode = 404;}Factory constructors
Section titled “Factory constructors”Un factory constructor peut renvoyer une instance existante :
class Singleton { static final Singleton _singleton = Singleton._internal();
factory Singleton() { return _singleton; }
Singleton._internal();}Héritage et implémentation
Section titled “Héritage et implémentation”Par défaut, toute class est implicitement une interface. Il est donc possible de l’implémenter ou de l’étendre.
class A { void hello() { print('hello'); }}
final a = A();a.hello();
class B extends A {}
final b = B();b.hello();
class C implements A { void hello() { print('HELLO'); }}
final c = C();c.hello();Classes abstraites
Section titled “Classes abstraites”- On ne peut pas les instancier
- Toutes les méthodes n’ont pas besoin d’être implémentées
abstract class BaseService { void describe();
void describeWithEmphasis() { print('========='); describe(); print('========='); }}En savoir plus sur les classes abstraites
Mixins
Section titled “Mixins”Les mixins permettent d’ajouter un même comportement à d’autres classes.
mixin Piloted { int astronauts = 1;
void describeCrew() { print('Number of astronauts: $astronauts'); }}
class PilotedCraft extends Spacecraft with Piloted { // ...}Les enums de Dart ont longtemps été très basiques. Dart 2.15 a ajouté quelques utilitaires :
MyEnum.values.asNameMap()MyEnum.option1.name
enum Orientation { landscape, portrait }
enum CardType { visa, mastercard }
void main() { print(CardType.values); print(CardType.values.asMap()); print(CardType.values.asNameMap()); print(CardType.visa); print(CardType.visa.name); print(CardType.visa.index); print(CardType.values.byName('visa'));}Depuis Dart 2.17, les enums peuvent avoir des valeurs associées. Le constructeur doit être constant et les valeurs associées final.
enum Routes { home('/'), login('/login'), details('/details');
final String path; const Routes(this.path);}
void main() { final route = Routes.home; switch(route) { case Routes.login: goToLogin(route.path); case Routes.home: goToHome(route.path); case Routes.details: goToDetails(route.path); }}Extensions
Section titled “Extensions”Les extensions de méthodes permettent d’ajouter des méthodes et propriétés à d’autres classes. À la différence des mixins, les extensions ont accès aux propriétés et méthodes de la classe qu’elles étendent.
extension SpaceCraftExtension on SpaceCraft { String get description => '$name / $launchDate';}Sealed classes
Section titled “Sealed classes”Permet de définir une catégorie de classes énumérables.
sealed class Message { final String title; Message(this.title);}
class TextMessage extends Message { TextMessage(super.title);}
class VideoMessage extends Message { VideoMessage(super.title);}
Widget buildMessageView(Message message) { return switch (message) { TextMessage() => Text('Text: ${message.title}'), VideoMessage() => VideoPlayer(message.title), };}Programmation asynchrone
Section titled “Programmation asynchrone”Futures
Section titled “Futures”Les API asynchrones renvoient principalement des Future. Les Futures sont l’équivalent des Promise en JavaScript. Il s’agit d’une valeur indéterminée au moment de l’affectation, mais qui sera potentiellement résolue ultérieurement, soit avec une donnée, soit avec une erreur.
Deux syntaxes permettent de les utiliser :
.then()async/await
future.then()
Section titled “future.then()”void main() { final data = _loadData(); print(data);
data.then( (value) => print(value), onError: (error) => print('ERROR : $error'), );}
Future<String> _loadData() { //return Future.value('Hello'); return Future.delayed(const Duration(seconds: 1), () => 'Hello'); //return Future.error('_loadData Error');}async-await
Section titled “async-await”void main() async { final version = await loadVersion(); print(version);}
Future<String> loadVersion() => Future.delayed(const Duration(seconds: 1), () => '1.0');En savoir plus sur l’asynchronie avec Dart
Erreurs et Exceptions
Section titled “Erreurs et Exceptions”Lancer des exceptions
Section titled “Lancer des exceptions”if (astronauts == 0) { throw StateError('No astronauts.');}Gérer les exceptions
Section titled “Gérer les exceptions”void main(List<String> arguments) async { try { for (var path in arguments) { var content = await File(path).readAsString(); print(content); } } on IOException catch (e, stacktrace) { print('Could not read: $e\n$stacktrace'); } catch (e, stacktrace) { print('Erreur : $e\n$stacktrace'); } finally { print('Done !'); }}Packages et Dart Analyzer
Section titled “Packages et Dart Analyzer”Packages
Section titled “Packages”Les packages Dart peuvent être importés depuis différentes sources :
Depuis pub.dev :
dependencies: basics: ^1.0.0Depuis GitHub :
dependencies: basics: git: https://github.com/user/repo.gitDepuis un chemin local :
dependencies: basics: path: ../local/pathDepuis un repo privé :
dependencies: basics: hosted: name: basics url: https://my.host.com version: ^1.0.0Gestionnaire de packages
Section titled “Gestionnaire de packages”Le gestionnaire de packages Dart est pub. Pour récupérer les dépendances :
- Projet Dart :
pub get - Projet Flutter :
flutter packages get
Pour ajouter un package :
- Projet Flutter :
flutter packages add <package_name> - Projet Flutter :
flutter packages upgrade
Imports
Section titled “Imports”// Importing core librariesimport 'dart:math';
// Importing libraries from external packagesimport 'package:test/test.dart';
// Importing filesimport 'path/to/my_other_file.dart';Cf. https://dart.dev/tools/pub/dependencies
Dart Analyzer
Section titled “Dart Analyzer”Il est possible de personnaliser l’analyzer Dart via le fichier (facultatif) analysis_options.yaml.
Pedantic
Section titled “Pedantic”Pedantic est un ensemble de bonnes pratiques renforcées via le linter intégré.
Règles de lint
Section titled “Règles de lint”Consultez les règles de lint pour personnaliser votre configuration.
Liens utiles
Section titled “Liens utiles”TP Dart
Section titled “TP Dart”Projet pratique : Créer une application console Dart pour convertir JSON en CSV.
import 'dart:io'; // Fileimport 'dart:convert'; // jsonDecode
// dart bin/json_csv.dart /chemin/users.jsonvoid main(List<String> arguments) async { // 1. récupération du chemin // 2. lecture du contenu du fichier cf File(path) // 3. parsing json cf. jsonDecode // 4. transformation données vers CSV // 5. enregistrement fichier CSV}Le fichier de test :
[ { "id": 1, "name": "Alice", "email": "alice@example.com", "age": 28 }, { "id": 2, "name": "Bob", "email": "bob@example.com", "age": 34 }, { "id": 3, "name": "Charlie", "email": "charlie@example.com", "age": 22 }]