Blog getting-started-with-flutter
softwareNOVEMBER 21, 2023

Getting Started with Flutter

kraaakilo's avatar

Comment débuter avec le framework Flutter ?

Introduction à Flutter : L’outil de développement d’applications mobile et desktop

Flutter est un framework open-source de Google pour la création d'applications mobiles, web et de bureau à partir d'un code source unique. Il est basé sur le langage de programmation Dart, qui a été également développé par Google. Flutter permet aux développeurs de créer des applications cross-platform de haute qualité avec un code simple et efficace. Il offre une large gamme de widgets personnalisables qui permettent de créer des interfaces utilisateur riches et expressives. En outre, Flutter dispose d'une fonctionnalité de "Hot Reload" qui permet de voir instantanément les changements que vous apportez au code dans l'application en cours d'exécution, ce qui facilite le processus de développement et de débogage. Flutter est également connu pour sa grande communauté qui offre un support et des ressources en ligne, ainsi que pour sa documentation complète et facilement accessible. En somme, Flutter est un outil puissant pour les développeurs qui cherchent à créer des applications cross-platform modernes et performantes.

Configurer l’environnement de développement

Afin d'installer le framework Flutter et commencer à créer des applications cross-platform, il est nécessaire de configurer son environnement de développement pour une expérience agréable.

Pour cela rendez-vous sur le site : https://docs.flutter.dev/get-started/install et suivez minutieusement les étapes pour installe flutter sur votre système.

Se familiariser avec la philosophie flutter

  • Comprendre le fonctionnement des widgets

    Comprendre le fonctionnement des widgets Flutter est essentiel pour développer des applications efficaces et personnalisées avec leframework. Flutter est un framework open-source de Google pour la création d'applications mobiles, web et de bureau à partir d'un code source unique. Les widgets sont les éléments de base qui composent l'interface utilisateur dans Flutter. Voici une explication simplifiée de leur fonctionnement :

    • Widgets en tant qu'éléments de l'interface utilisateur : Dans Flutter, tout est un widget. Les boutons, les textes, les images, les mises en page, etc., sont tous des widgets. Ils sont utilisés pour construire l'interface utilisateur de votre application.
    • Hiérarchie des widgets : Les widgets sont organisés dans une hiérarchie, généralement sous forme d'arbre, où chaque widget a un parent et peut avoir des enfants. Cette hiérarchie représente la structure de votre interface utilisateur.
    • Widgets immuables : Les widgets sont immuables, ce qui signifie qu'une fois créés, ils ne peuvent pas être modifiés. Si vous voulez changer quelque chose dans l'interface utilisateur, vous créez un nouveau widget avec les modifications souhaitées.
    • Reconstruction : Lorsque vous effectuez une modification dans votre interface utilisateur, Flutter reconstruit uniquement les parties de l'arbre de widget qui ont changé. Cela rend l'application rapide et efficace.
    • Widgets stateless et stateful : Il existe deux types principaux de widgets : les widgets stateless (sans état) et les widgets stateful (avec état). Les widgets stateless sont immuables et ne changent pas en fonction des interactions de l'utilisateur. Les widgets stateful peuvent changer leur état en réponse à des interactions utilisateur (comme les boutons qui changent de texte).
    • Gestion de l'état : Pour gérer l'état d'un widget stateful, vous utilisez des classes d'état (State) qui sont associées à ces widgets. L'état peut être modifié pour déclencher des reconstructions de widget.
    • Composition de widgets : Vous pouvez composer des widgets les uns dans les autres pour construire des interfaces utilisateur complexes. Cela permet de créer des mises en page flexibles et des éléments d'interface utilisateur personnalisés.
    • Widgets pré-construits : Flutter offre une bibliothèque de widgets pré-construits pour vous aider à créer rapidement des interfaces utilisateur courantes, comme des boutons, des listes, des barres de navigation, etc.
    • Personnalisation : Vous pouvez personnaliser les widgets en ajustant leurs propriétés et en utilisant des thèmes pour modifier l'apparence globale de l'application.
    • Hot Reload : Flutter propose une fonctionnalité de "Hot Reload" qui permet de voir instantanément les changements que vous apportez au code dans l'application en cours d'exécution, ce qui facilite le processus de développement et de débogage.
  • Comprendre comment gérer l’état global d’un widget et de l’application

    1. InheritedWidget : L'InheritedWidget est un widget qui permet de propager des données de manière efficace et réactive à travers l'arbre des widgets sans avoir besoin de les passer explicitement à chaque widget fils. Vous pouvez créer une classe personnalisée qui étend InheritedWidget pour stocker l'état global de votre application. Ensuite, vous pouvez accéder à cet état depuis n'importe quel widget descendant en utilisant BuildContext.
    2. Provider : Le package provider est une bibliothèque Flutter populaire pour la gestion de l'état global. Il utilise l'approche InheritedWidget sous-jacente pour fournir un moyen simple de gérer l'état global. Vous pouvez créer une classe modèle, la fournir à l'arbre des widgets à l'aide du ChangeNotifierProvider, puis écouter les changements dans les widgets fils avec Consumer.
    3. Voici un lien utile vers la documentation officielle de flutter sur les deux types de widgets principaux dans l’outil : Les StateLess Widgets et les StateFul Widgets. Add interactivity to your Flutter app

Configuration du thème de l’application (Couleurs, Polices etc…)

Pour partager des couleurs et des styles de police dans une application flutter, on utilise des thèmes.

On peut définir des thèmes pour l'ensemble de l'application. On peut étendre un thème pour modifier le style d'un composant. Chaque thème définit les couleurs, le style de type et d'autres paramètres applicables au type de composant Material (Design System).

Flutter applique le style dans l'ordre suivant :

Styles appliqués au widget spécifique. Thèmes qui remplacent le thème parent immédiat. Thème principal pour l'ensemble de l'application. Après avoir défini un thème, on peut l'utiliser dans nos propres widgets. Les widgets Material de Flutter utilisent notre configuration de thème pour définir les couleurs d'arrière-plan et les styles de police des barres d'application, des boutons, des cases à cocher, etc.

Comment créer un thème et l’adapter à notre application :

Use themes to share colors and font styles

Navigation entre des pages

  • Accédez à un nouvel écran et revenir en arrière

class FirstRoute extends StatelessWidget {
  const FirstRoute({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('First Route'),
      ),
      body: Center(
        child: ElevatedButton(
          child: const Text('Open route'),
          onPressed: () {
            // Navigate to second route when tapped.
          },
        ),
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  const SecondRoute({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Second Route'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // Navigate back to first route when tapped.
          },
          child: const Text('Go back!'),
        ),
      ),
    );
  }
}

Sur ce bout de code deux widgets représentant des pages sont affichés.

Nous allons maintenant implémenter les deux fonctions :

// Dans le widget FirstRoute
onPressed: () {
  Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => const SecondRoute()),
  );
}

On utilise la méthode push du widget Navigator pour ajouter à la pile de routes actuelle la nouvelle page. Cela est placé dans le context (BuildContext) qui est obtenu lors de la création du widget dans la méthode build;

// Dans le second Widget
onPressed: () {
  Navigator.pop(context);
}

Ce bout de code exécuté dans la fonction onPressed va supprimer le dernier widget dans la stack de navigation du context courant. Cela va donc ramener à la Page FirstRoute.

Pour plus d’informations consulter :

Navigate to a new screen and back

  • Navigation avec des routes nommées

Pour utilise la navigation avec des routes nommées, on définit les routes en fournissant des propriétés supplémentaires au constructeur MaterialApp : la route initiale et les routes elles-mêmes.

La propriété initialRoute définit la route par laquelle l'application doit démarrer. La propriété routes définit les routes nommées disponibles et les widgets à construire lors de la navigation vers ces routes.

MaterialApp(
  title: 'Named Routes Demo',
  // Start the app with the "/" named route. In this case, the app starts
  // on the FirstScreen widget.
  initialRoute: '/',
  routes: {
    // When navigating to the "/" route, build the FirstScreen widget.
    '/': (context) => const FirstScreen(),
    // When navigating to the "/second" route, build the SecondScreen widget.
    '/second': (context) => const SecondScreen(),
  },
)

Pour naviguer vers la page (SecondScreen), on pourra utiliser le snippet suivant :

Navigator.pushNamed(context, '/second');

Référence de la section : Navigate with named routes

  • Passer des arguments à une route nommée

Send data to a new screen

On déclare dans le constructeur du widget, la data à reçevoir. Ensuite on envoie cette data grâce au MaterialPageBuilder.

onTap: () {
  Navigator.push(
    context,
    MaterialPageRoute(
      builder: (context) => DetailScreen(data: variabe_a_envoyer),
    ),
  );
},

Gestion des appels API

  • Récupérer des données sur Internet

    Pour récupérer des données depuis Internet dans une application Flutter, on a besoin de faire des requêtes HTTP vers des serveurs distants. Pour cela, Flutter dispose de plusieurs packages qui simplifient ce processus. Voici une étape par étape pour récupérer des données depuis Internet dans une application Flutter :

    1. Ajouter un package HTTP : Il faut ajouter le package http au projet avec la commande :

      flutter pub add http
      

      Ensuite, exécutez flutter pub get dans votre terminal pour télécharger le package.

    2. Importez le package HTTP : Dans votre fichier Dart où vous souhaitez effectuer des requêtes HTTP, importez le package http :

      import 'package:http/http.dart' as http;
      
    3. Effectuez une requête HTTP : Vous pouvez maintenant utiliser le package http pour effectuer des requêtes HTTP. Voici un exemple de requête GET pour récupérer des données depuis une API REST :

      Future<void> fetchData() async {
        final response = await http.get(Uri.parse('https://exemple.com/api/data'));
        if (response.statusCode == 200) {
          // La requête a réussi, vous pouvez traiter les données ici.
          print('Données récupérées : ${response.body}');
        } else {
          // Gestion des erreurs en cas d'échec de la requête.
          print('Échec de la requête : ${response.statusCode}');
        }
      }
      
      
    4. Utilisez le résultat (Conversion en JSON valide) : Une fois que vous avez récupéré les données, vous pouvez les utiliser dans votre application. Généralement, vous les convertirez en objets Dart pour une utilisation plus facile.

      import 'dart:convert';
      
      // ...
      
      Future<void> fetchData() async {
        final response = await http.get(Uri.parse('<https://exemple.com/api/data>'));
        if (response.statusCode == 200) {
          final jsonData = json.decode(response.body);
          // Utilisez les données JSON pour créer des objets Dart ou effectuer des opérations.
        } else {
          print('Échec de la requête : ${response.statusCode}');
        }
      }
      
      
    5. Gérez les erreurs et la gestion de l'état : Lors de la récupération de données depuis Internet, il est important de gérer les erreurs de manière appropriée, par exemple, en affichant un message d'erreur à l'utilisateur ou en effectuant une action de secours en cas d'échec de la requête.

    6. Appelez la fonction fetchData() où vous en avez besoin : Vous pouvez maintenant appeler la fonction fetchData() dans vos widgets Flutter pour récupérer les données et les afficher dans votre interface utilisateur.

    7. Assurez-vous de demander les autorisations Internet : Dans votre fichier AndroidManifest.xml (pour Android) et Info.plist (pour iOS), assurez-vous que les autorisations Internet sont demandées si elles ne le sont pas déjà. Cela permettra à votre application d'accéder à Internet.

Fetch data from the internet

  • Faire des requêtes authentifiées

    Pour effectuer une requête HTTP authentifiée, vous devez inclure les informations d'authentification dans l'en-tête (header) de la requête. Voici comment effectuer une requête GET authentifiée avec un jeton d'accès (par exemple, un jeton JWT) :

    Future<void> fetchAuthenticatedData(String token) async {
      final url = Uri.parse('https://exemple.com/api/data');
      final headers = {
        'Authorization': 'Bearer $token', // Utilisez votre jeton d'accès ici.
      };
    
      final response = await http.get(url, headers: headers);
    
      if (response.statusCode == 200) {
        // Traitement des données en cas de réussite.
        print('Données récupérées : ${response.body}');
      } else {
        // Gestion des erreurs en cas d'échec de la requête.
        print('Échec de la requête : ${response.statusCode}');
      }
    }
    
    

Persistance des données

  • Conserver les données avec SQLite

    On peut conserver les données avec SQLite en utilisant cette base de données légère et intégrée dans les applications Flutter. Pour commencer, on doit ajouter la dépendance SQLite au projet Flutter. Ensuite, on peut utiliser le package sqflite pour créer, lire, mettre à jour et supprimer des données.

    On commence par créer une classe qui gère la base de données, en définissant le schéma de la table et les opérations CRUD (Create, Read, Update, Delete). On peut utiliser le package path pour obtenir le chemin du fichier de base de données sur l'appareil.

    Voici un exemple de code pour créer une base de données SQLite en Flutter contenant une classe et différentes méthodes CRUD :

    import 'package:sqflite/sqflite.dart';
    import 'package:path/path.dart';
    
    class DatabaseHelper {
      static final DatabaseHelper _instance = DatabaseHelper.internal();
    
      factory DatabaseHelper() => _instance;
    
      static Database? _db;
    
      Future<Database> get db async {
        if (_db != null) {
          return _db!;
        }
        _db = await initDb();
        return _db!;
      }
    
      DatabaseHelper.internal();
    
      Future<Database> initDb() async {
        String databasesPath = await getDatabasesPath();
        String path = join(databasesPath, 'my_database.db');
    
        // Ouvrir la base de données (ou la créer si elle n'existe pas encore)
        var db = await openDatabase(path, version: 1, onCreate: _onCreate);
        return db;
      }
    
      void _onCreate(Database db, int newVersion) async {
        // Créer la table et définir son schéma
        await db.execute('''
          CREATE TABLE my_table (
            id INTEGER PRIMARY KEY,
            name TEXT,
            age INTEGER
          )
        ''');
      }
    
      // Ajouter une entrée dans la base de données
      Future<int> insertData(Map<String, dynamic> data) async {
        var database = await db;
        return await database.insert('my_table', data);
      }
    
      // Lire les données de la base de données
      Future<List<Map<String, dynamic>>> getData() async {
        var database = await db;
        return await database.query('my_table');
      }
    
      // Mettre à jour une entrée dans la base de données
      Future<int> updateData(Map<String, dynamic> data) async {
        var database = await db;
        return await database.update('my_table', data,
            where: 'id = ?', whereArgs: [data['id']]);
      }
    
      // Supprimer une entrée de la base de données
      Future<int> deleteData(int id) async {
        var database = await db;
        return await database.delete('my_table', where: 'id = ?', whereArgs: [id]);
      }
    }
    
    
  • Stocker les données clé - valeur sur le disque

    Pour stocker des données clé-valeur sur le disque en Flutter, on peut utiliser le package shared_preferences. Ce package permet de stocker des données simples telles que des chaînes de caractères, des booléens, des entiers, etc., de manière persistante et facilement accessible. Voici comment on peut l'utiliser :

    1. Ajouter la dépendance au projet:

    flutter pub add shared_preferences
    

    2. Importer le package shared_preferences dans ton fichier Dart :

    import 'package:shared_preferences/shared_preferences.dart';
    
    

    3. Utiliser SharedPreferences pour stocker et récupérer des données :

    Voici comment on peut stocker des données clé-valeur :

    // Écriture des données
    Future<void> saveData(String key, dynamic value) async {
      final prefs = await SharedPreferences.getInstance();
      if (value is String) {
        prefs.setString(key, value);
      } else if (value is int) {
        prefs.setInt(key, value);
      } else if (value is double) {
        prefs.setDouble(key, value);
      } else if (value is bool) {
        prefs.setBool(key, value);
      }
      // Ajoute d'autres types de données si nécessaire
    }
    
    

    Pour récupérer des données, on utilise la méthode correspondante au type de données stocké :

    // Lecture des données
    Future<dynamic> loadData(String key) async {
      final prefs = await SharedPreferences.getInstance();
      if (prefs.containsKey(key)) {
        return prefs.get(key);
      } else {
        return null; // Clé non trouvée
      }
    }
    
    

    4. Exemple d'utilisation :

    void main() async {
      // Stocker des données
      await saveData('nom', 'John Doe');
      await saveData('age', 30);
    
      // Récupérer des données
      final name = await loadData('nom');
      final age = await loadData('age');
    
      print('Nom: $name');
      print('Âge: $age');
    }
    
    

Store key-value data on disk

Gestion des formulaires

  • Construire un formulaire avec validation

    On peut construire un formulaire avec validation en utilisant Flutter. Les formulaires sont un élément essentiel pour collecter des données auprès des utilisateurs. Voici les étapes générales pour créer un formulaire avec validation en Flutter :

    1. Créer un formulaire : Utilisez le widget Form pour créer un formulaire. À l'intérieur du formulaire, vous pouvez ajouter différents champs de saisie comme TextFormField pour collecter des données.

    2. Ajouter des validateurs : Pour chaque champ de saisie, vous pouvez ajouter des validateurs. Les validateurs sont des fonctions qui vérifient si les données saisies sont valides. Par exemple, vous pouvez utiliser validator dans TextFormField :

      TextFormField(
        validator: (value) {
          if (value.isEmpty) {
              // Gérer d'autres validations ici si nécessaire
            return 'Ce champ est requis';
          }
          // Ajoutez d'autres validations personnalisées ici
          return null; // Retourne null si la validation est réussie
        },
        // Autres propriétés du champ de saisie
      )
      
    3. Soumettre le formulaire : Utilisez un bouton de soumission, généralement un bouton ElevatedButton ou TextButton. Dans le gestionnaire de soumission, vérifiez si le formulaire est valide en utilisant FormKey (ce formKey doit être bindé au widget Form) et ensuite traitez les données.

      final formKey = GlobalKey<FormState>();
      Form(
        key : formKey,
        child : ...
      )
      
      ElevatedButton(
        onPressed: () {
          if (formKey.currentState.validate()) {
            // Les données du formulaire sont valides, traitez-les ici
          }
        },
        child: Text('Valider'),
      )
      
    4. Afficher les erreurs de validation : Pour afficher les erreurs de validation, vous pouvez utiliser Text ou un autre widget pour afficher les messages d'erreur à côté des champs de saisie. Cela constitue une base pour la création d'un formulaire avec validation en Flutter. Vous pouvez personnaliser davantage en fonction de vos besoins spécifiques. Build a form with validation

  • Récupérer la valeur d'un champ de texte

    Pour récupérer la valeur d'un champ de texte en Flutter, on peut utiliser le contrôleur (TextEditingController) associé au champ de texte. Voici comment faire cela :

    1. Créer un contrôleur (TextEditingController) pour le champ de texte :

      TextEditingController monControleur = TextEditingController();
      
    2. Associer le contrôleur au champ de texte dans le widget TextFormField :

      TextFormField(
        controller: monControleur,
        // Autres propriétés du champ de texte
      )
      
      
    3. Pour récupérer la valeur du champ de texte à un moment donné, on peut simplement utiliser monControleur.text. Par exemple, pour afficher la valeur dans un widget Text :

      Text('La valeur du champ de texte est : ${monControleur.text}')
      
      
    4. N'oubliez pas de libérer les ressources du contrôleur lorsque vous n'en avez plus besoin. Cela peut se faire dans la méthode dispose de votre widget, généralement dans le cycle de vie du widget (par exemple, dans dispose() de StatefulWidget) :

      @override
      void dispose() {
        monControleur.dispose();
        super.dispose();
      }
      
      

    En utilisant cette approche, on peut récupérer et manipuler la valeur d'un champ de texte à tout moment dans l’application Flutter.

Types de données : Listes

  • Créer et gérer des listes d’éléments

    Pour créer et gérer des listes d'éléments en Flutter, on peut utiliser le widget ListView ou ses dérivés, tels que ListView.builder ou ListView.separated. Voici comment procéder :

  • Créer une liste simple de widgets

    1. Utiliser le widget ListView pour créer une liste verticale d'éléments. Par exemple :

      ListView(
        children: <Widget>[
          ListTile(title: Text('Élément 1')),
          ListTile(title: Text('Élément 2')),
          ListTile(title: Text('Élément 3')),
          // Ajoutez autant d'éléments que nécessaire
        ],
      )
      
      

      Cela créera une liste verticale d'éléments, chaque élément étant un widget ListTile avec un texte.

  • Créer une liste dynamique avec ListView.builder

    Si vous avez une liste dynamique d'éléments, utilisez ListView.builder. Cela est utile lorsque vous avez une liste longue ou que les éléments doivent être générés à la volée.

    1. Créez une liste de données, par exemple une liste de chaînes :
    List<String> elements = ['Élément 1', 'Élément 2', 'Élément 3'];
    
    
    1. Utilisez ListView.builder pour créer une liste basée sur les données :
    ListView.builder(
      itemCount: elements.length,
      itemBuilder: (context, index) {
        return ListTile(title: Text(elements[index]));
      },
    )
    
    

    Ici, itemCount est défini sur la longueur de votre liste de données, et itemBuilder est une fonction qui génère chaque élément de la liste.

  • Gérer des listes avec séparateurs

    Si vous souhaitez ajouter des séparateurs entre les éléments de la liste, utilisez ListView.separated.

    1. Utilisez ListView.separated avec un separatorBuilder pour spécifier le séparateur :
    ListView.separated(
      itemCount: elements.length,
      separatorBuilder: (context, index) => Divider(), // Séparateur
      itemBuilder: (context, index) {
        return ListTile(title: Text(elements[index]));
      },
    )
    
    

    Dans cet exemple, Divider() est utilisé comme séparateur entre les éléments de la liste.

    En suivant ces étapes, vous pouvez créer et gérer des listes d'éléments de manière efficace dans votre application Flutter. Assurez-vous d'adapter ces exemples en fonction de vos besoins spécifiques.

    Une documentation plus détaillée est disponible sur le site officiel de Flutter :

    Lists

Déploiement de l’application

Pour déployer finalement votre application, on passe d’abord par une étape essentielle : L’obsfucation du code Dart : L’obsfucation du code est le processus de modification du binaire d'une application afin de le rendre plus difficile à comprendre pour les humains. L’obsfucation cache les noms de fonctions et de classes dans votre code Dart compilé, en remplaçant chaque symbole par un autre symbole, ce qui rend difficile pour un attaquant de faire de l'ingénierie inverse sur votre application.

Il suffit d’exécuter cette commande dans le répertoire de votre projet pou build une version “obsfucate” de l’application android

flutter build apk --split-per-abi --obsfucate

Les informations additionnelles sont répertoriées sur cette page :

Obfuscate Dart code

Pour déployer sous n’importe quelle plateforme flutter met à notre disposition des instructions bien détaillées (de la signature de l’application jusqu’à la publication dans les différents stores.)

Deployment

Ceci reste une liste des fonctionnalités principales dont une application a besoin pour fonctionner de manière basique. L’ajout de fonctionnalités supplémentaires ( niveau interface utilisateur, ou fonctionnement interne de l’application) peut nécessiter une lecture plus approfondie de la documentation du framework. Notons aussi l’existence de FlutterFlow qui permet de générer des interfaces utilisateurs pour flutter avec une expérience développeur no-code.

Référence utilisée : https://flutter.dev

Let's connect

Stay in the loop with my latest projects and insights! Follow me on Twitter to catch all the updates as they happen. Don't miss out on the journey – let's connect and explore the world of tech together. Click to follow now!