Dart: The Essentials for Flutter

First lesson of the Mobile Apps with Flutter course, free to read: about 18 minutes, 3 exercises and 3 quiz questions. This is the catalogue's base lesson; in the student space, it is rewritten for your profile.

Objectives

  • Declare typed variables with var, final and const and use null safety
  • Write functions with named parameters and classes with constructors
  • Work with lists and maps and write asynchronous code with Future, async and await

Variables and typesNull safetyFunctions and classesFuture, async and await

Why Dart

Flutter, Google’s framework for building iOS, Android, web and desktop apps from a single codebase, is written in Dart. Dart resembles JavaScript, Java or C#: if you know programming basics (variables, conditions, loops, functions), you will read it quickly.

To try it without installing anything, use DartPad (dartpad.dev) in the browser. To develop, install the Flutter SDK (it includes Dart) and check the installation with flutter doctor.

void main() {
  print('Hello Flutter');
}

Every program starts with the main function. Statements end with ;.

Variables and types

var ville = 'Lyon';          // inferred type: String
int quantite = 3;
double prix = 19.9;
bool enStock = true;
final dateCommande = DateTime.now();   // can no longer be reassigned
const tauxTva = 0.2;                   // known at compile time
  • var: the type is inferred from the value and no longer changes (ville = 3 is an error).
  • final: the variable receives a value only once, possibly computed at runtime.
  • const: the value is known at compile time; Flutter uses it to reuse identical widgets.

Strings are written between single or double quotes and are interpolated: 'Total: $quantite items', 'Incl. tax: ${prix * 1.2}'.

Null safety

By default, a variable cannot be null. To allow it, add ? to the type:

String nom = 'Léa';      // never null
String? telephone;       // can be null (is null here)

print(telephone?.length);        // null: ?. calls nothing if the value is null
print(telephone ?? 'not provided');   // ?? provides a fallback value
int longueur = telephone!.length;      // !: "I guarantee it's not null" — crashes otherwise

The compiler rejects telephone.length without a safeguard: most “null” errors are caught before execution. Avoid ! unless you are certain of the value; an if (telephone != null) check then lets you use telephone as a String.

Functions and named parameters

double prixTtc(double ht, {double taux = 0.2}) => ht * (1 + taux);

String etiquette({required String nom, required double prix, bool promo = false}) {
  return promo ? '$nom (sale): ${prix.toStringAsFixed(2)} €' : '$nom: ${prix.toStringAsFixed(2)} €';
}

void main() {
  print(prixTtc(100));                         // 120.0
  print(etiquette(nom: 'Casque', prix: 39));   // Casque: 39.00 €
}

Named parameters, between curly braces, are called by name; required makes them mandatory. Flutter uses them everywhere: Text('Hello', style: …), Padding(padding: …, child: …). The arrow => abbreviates a function that returns a single expression.

Classes

class Produit {
  final String nom;
  final double prix;
  final int stock;

  const Produit({required this.nom, required this.prix, this.stock = 0});

  bool get disponible => stock > 0;          // computed getter

  @override
  String toString() => 'Produit($nom, $prix)';
}

final casque = Produit(nom: 'Casque', prix: 39, stock: 4);
print(casque.disponible);   // true

this.nom in the constructor parameters directly initializes the field. final fields and a const constructor make the object immutable, which is the norm for data in Flutter.

Collections

final produits = [
  Produit(nom: 'Casque', prix: 39, stock: 4),
  Produit(nom: 'Clavier', prix: 59),
];
final noms = produits.where((p) => p.disponible).map((p) => p.nom).toList();   // [Casque]
final stocks = <String, int>{'Casque': 4, 'Clavier': 0};                       // Map
print(stocks['Casque']);   // 4; a missing key gives null

where filters, map transforms; both return a lazy Iterable, which toList() converts into a list.

Asynchronous code: Future, async, await

A long operation (network, file) returns a Future: a value available later.

Future<String> chargerClient(int id) async {
  await Future.delayed(const Duration(milliseconds: 300));   // simulates the network
  if (id <= 0) throw ArgumentError('Invalid identifier');
  return 'Client $id';
}

Future<void> main() async {
  try {
    print(await chargerClient(7));   // Client 7
    await chargerClient(0);
  } catch (e) {
    print('Error: $e');
  }
}

await suspends the async function without blocking the interface, and try/catch catches errors. You will find exactly this model again for API calls.

Key takeaways

  • var, final, const; non-null types by default, ?, ?., ??, ! with caution.
  • Named parameters {required …}, immutable classes with final and const.
  • where/map/toList, Future + async/await + try/catch.

Exercises

Solutions and tests stay on the server. In the student space, your code is run and checked, and the tutor gives hints before the solution.

  1. TaskDifficulty 12 hints

    Available products and total including tax

    In DartPad (or with dart run locally), write a Produit class with the fields final String nom, final double prix and final int? stock, a constructor const Produit(this.nom, this.prix, {this.stock}) and a method double prixTtc([double taux = 0.2]).

    In main, create this list:

    nom,prix,stock
    Casque,40,3
    Clavier,50,(aucun)
    Souris,25,0

    Then print, with two print calls:

    1. the list of the names of products whose stock is strictly positive (a missing stock counts as 0);
    2. the total including tax of the three products with toStringAsFixed(2).
    • The program compiles without any null safety warning
    • The missing stock is handled with ?? 0 and not with !
    • The output has exactly two lines
  2. TaskDifficulty 22 hints

    Asynchronous execution order

    Run this program in DartPad, then complete the chargerNom function so that it waits 100 ms, throws Exception('introuvable') if id == 0 and otherwise returns 'Client $id'.

    Future<String> chargerNom(int id) async {
      // à compléter
    }
    
    Future<void> main() async {
      print('Début');
      final nom = await chargerNom(7);
      print(nom);
      try {
        await chargerNom(0);
      } catch (e) {
        print('Erreur : $e');
      }
      print('Fin');
    }
    • chargerNom uses await Future.delayed(const Duration(milliseconds: 100))
    • The error is thrown with throw Exception('introuvable')
    • The output has four lines in the expected order
  3. Multiple choiceDifficulty 11 hint

    Null safety operators

    What does this code print?

    String? ville;
    print(ville?.length ?? 0);
    • null
    • 0
    • A runtime error
    • A compile error

End-of-lesson quiz

  1. Which declaration accepts the value null?

    • String nom;
    • String? nom;
    • final String nom = '';
  2. How do you make a named parameter mandatory?

    • With the required keyword
    • With !
    • By placing it first
  3. What does an async function that returns an int return?

    • int
    • Future<int>
    • Stream<int>

The rest of the course

  1. Widgets and Layout18 min
  2. State and Interactions20 min
  3. Networking and JSON Data18 min
  4. Preparing to Publish a Flutter App16 min

Mobile Apps with Flutter · Intermediate · 1 h 30 min