L'application finale que vous allez créer

Dans ce tutoriel approfondi, vous allez apprendre comment créer une application complète de gestion de tâches en utilisant Flutter et Firebase. Vous serez guidé pas à pas à travers l'ensemble du processus, de l'installation et la configuration de Flutter, à la création d'un nouveau projet, en passant par la mise en place et l'intégration avec Firebase.

À la fin de ce tutoriel, vous aurez non seulement une application de gestion de tâches entièrement fonctionnelle, mais vous aurez également acquis des compétences précieuses en développement Flutter et Firebase, qui sont extrêmement demandées sur le marché du travail actuel.

Que vous soyez un débutant qui cherche à se lancer dans le développement d'applications mobiles ou un développeur expérimenté qui cherche à élargir ses compétences, ce tutoriel a beaucoup à offrir. Alors, préparez-vous, mettez vos mains dans le code et commençons ce voyage passionnant de création d'applications avec Flutter et Firebase !

Étape 1: Installation et configuration de Flutter

1.1. Allez sur le site Web officiel de Flutter et téléchargez la dernière version stable de Flutter SDK pour votre système d'exploitation.

1.2. Une fois le téléchargement terminé, extrayez le fichier zip dans le répertoire souhaité.

1.3. Ajoutez Flutter au PATH de votre système. Pour cela, modifiez le fichier .bashrc (pour Linux/Mac) ou Environment Variables (pour Windows) et ajoutez le chemin du répertoire bin de Flutter.

1.4. Assurez-vous que Flutter est correctement installé en exécutant la commande flutter doctor dans le terminal. Cette commande vérifie votre environnement et affiche un rapport sur l'état de votre installation de Flutter.

Étape 2: Création d'un nouveau projet Flutter

2.1. Pour créer un nouveau projet Flutter, ouvrez le terminal et naviguez vers le répertoire où vous souhaitez créer le projet.

2.2. Exécutez la commande flutter create task_manager. Ceci créera un nouveau projet Flutter avec le nom task_manager.

2.3. Naviguez dans le répertoire du projet en utilisant la commande cd task_manager.

2.4. Testez l'application en lançant la commande flutter run . Choisissez un device.

Voici le résultat.

Étape 3: Configuration de Firebase

Connectez-vous à votre compte Firebase et créez un nouveau projet. Une fois le projet créé, ajoutez une application à ce projet (Android ou iOS) et suivez les instructions pour enregistrer votre application. Ensuite, téléchargez le fichier google-services.json pour Android ou GoogleService-Info.plist pour iOS, et ajoutez-le à votre projet Flutter. Pour Android, le fichier va dans le dossier android/app de votre projet.

Étape 4: Ajout des dépendances Firebase à votre projet Flutter

Ouvrez le fichier pubspec.yaml dans la racine de votre projet Flutter. Sous dependencies, ajoutez les lignes suivantes :

dependencies:
  flutter:
    sdk: flutter
  firebase_core: "^2.1.1"
  cloud_firestore: "^4.0.3"

Sauvegardez le fichier et exécutez la commande suivante dans votre terminal pour obtenir les nouveaux packages :

flutter pub get

Étape 5: Initialisation de Firebase

Ouvrez le fichier lib/main.dart et ajoutez les imports suivants en haut du fichier :

import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

Puis, initialisez Firebase dans la fonction main en utilisant votre propre configuration Firebase :

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: const FirebaseOptions(
      appId: 'a changer',
      apiKey: 'a changer',
      projectId: 'a changer',
      messagingSenderId: '...',
      authDomain: '...',
    ),
  );
  runApp(MyApp());
}

Remplacez les placeholders 'a changer' et '...' par les informations de votre propre projet Firebase.

Étape 6: Création de votre modèle de données

Créez un nouveau fichier Dart dans le répertoire lib de votre projet et nommez-le task.dart. Dans ce fichier, définissez une classe Task avec les propriétés nécessaires. Par exemple :

class Task {
  final String title;
  final bool isDone;

  Task(this.title, {this.isDone = false});
}

Cette classe Task sera utilisée pour modéliser les tâches dans votre application.

Étape 7: Construction de l'interface utilisateur

Revenez au fichier lib/main.dart. Définissez une nouvelle classe Stateless MyApp. C'est le widget racine de votre application. Il doit renvoyer une instance de MaterialApp dans sa méthode build.

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Task Manager',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

Ensuite, définissez une autre classe Stateless HomePage. C'est la page principale de votre application. Dans sa méthode build, vous devez renvoyer une instance de Scaffold qui contient un AppBar et un ListView.

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Task Manager'),
      ),
      body: Center(child: Text('Hello World!')),
    );
  }
}

Enfin, utilisez un StreamBuilder pour lire les données de Firestore et afficher la liste des tâches.

class TaskList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
      stream:

FirebaseFirestore.instance.collection('tasks').snapshots(),
      builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
        if (snapshot.hasError) {
          return SomethingWentWrong();
        }

        if (snapshot.connectionState == ConnectionState.waiting) {
          return Loading();
        }

        return new ListView(
          children: snapshot.data!.docs.map((DocumentSnapshot document) {
            Map<String, dynamic> data = document.data() as Map<String, dynamic>;
            return ListTile(
              title: Text(data['title']),
              trailing: Checkbox(
                value: data['isDone'],
                onChanged: (bool? value) {
                  document.reference.update({'isDone': value});
                },
              ),
            );
          }).toList(),
        );
      },
    );
  }
}


class SomethingWentWrong extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('Une erreur est survenue.')),
    );
  }
}

class Loading extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: CircularProgressIndicator()),
    );
  }
}

Étape 8: Ajout de fonctionnalités à l'application

Dans le widget HomePage, vous pouvez ajouter une fonctionnalité pour permettre à l'utilisateur d'ajouter de nouvelles tâches à Firestore.

Pour ce faire, vous pouvez ajouter un TextFormField pour l'entrée de la tâche, et un ElevatedButton pour la soumission. Lorsque l'utilisateur appuie sur le bouton, vous pouvez prendre la valeur du TextFormField, créer un nouvel objet Task avec cette valeur, et ajouter cet objet à Firestore.

class AddTaskForm extends StatefulWidget {
  @override
  _AddTaskFormState createState() => _AddTaskFormState();
}

class _AddTaskFormState extends State<AddTaskForm> {
  final _formKey = GlobalKey<FormState>();
  final _taskController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: <Widget>[
          TextFormField(
            controller: _taskController,
            decoration: InputDecoration(
              labelText: 'Task',
            ),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter some text';
              }
              return null;
            },
          ),
          ElevatedButton(
            onPressed: () {
              if (_formKey.currentState!.validate()) {
                FirebaseFirestore.instance.collection('tasks').add({
                  'title': _taskController.text,
                  'isDone': false,
                });
                _taskController.clear();
              }
            },
            child: Text('Add Task'),
          ),
        ],
      ),
    );
  }
}

La classe AddTaskForm est utilisée pour créer un formulaire permettant à l'utilisateur d'ajouter de nouvelles tâches à l'application. Pour l'utiliser, vous pouvez l'ajouter en tant que child dans votre widget HomePage. Voici comment cela pourrait être fait :

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Task Manager'),
      ),
      body: Column(
        children: [
          AddTaskForm(),
          Expanded(child: TaskList()),
        ],
      ),
    );
  }
}

Dans ce code, le corps (body) du Scaffold est un widget Column, qui a deux enfants : AddTaskForm() et TaskList(). AddTaskForm() est le widget qui vous permet d'ajouter de nouvelles tâches, tandis que TaskList() est le widget qui affiche la liste des tâches existantes.

Notez l'utilisation du widget Expanded autour de TaskList(). C'est nécessaire parce que Column essaye de donner à chaque enfant autant d'espace qu'il en demande, mais dans le cas de ListView (qui est utilisé à l'intérieur de TaskList), il peut potentiellement demander un espace infini, car il peut dérouler à l'infini. Expanded dit à Column de donner à TaskList tout l'espace restant après avoir alloué l'espace pour tous les autres widgets.

Étape 9: Test de l'application

Enfin, testez votre application en exécutant la commande suivante dans votre terminal :

flutter run

Cela va construire et lancer votre application sur l'émulateur ou le dispositif physique connecté.

Le code complet du projet

// main.dart

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: const FirebaseOptions(
      appId: 'a changer',
      apiKey: 'a changer',
      projectId: 'a changer',
      messagingSenderId: '...',
      authDomain: '...',
    ),
  );
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Task Manager',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Task Manager'),
      ),
      body: Column(
        children: [
          AddTaskForm(),
          Expanded(child: TaskList()),
        ],
      ),
    );
  }
}

class TaskList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
      stream: FirebaseFirestore.instance.collection('tasks').snapshots(),
      builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
        if (snapshot.hasError) {
          return SomethingWentWrong();
        }

        if (snapshot.connectionState == ConnectionState.waiting) {
          return Loading();
        }

        return new ListView(
          children: snapshot.data!.docs.map((DocumentSnapshot document) {
            Map<String, dynamic> data = document.data() as Map<String, dynamic>;
            return ListTile(
              title: Text(data['title']),
              trailing: Checkbox(
                value: data['isDone'],
                onChanged: (bool? value) {
                  document.reference.update({'isDone': value});
                },
              ),
            );
          }).toList(),
        );
      },
    );
  }
}

class SomethingWentWrong extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('Une erreur est survenue.')),
    );
  }
}

class Loading extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: CircularProgressIndicator()),
    );
  }
}

class AddTaskForm extends StatefulWidget {
  @override
  _AddTaskFormState createState() => _AddTaskFormState();
}

class _AddTaskFormState extends State<AddTaskForm> {
  final _formKey = GlobalKey<FormState>();
  final _taskController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: <Widget>[
          TextFormField(
            controller: _taskController,
            decoration: InputDecoration(
              labelText: 'Task',
            ),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter some text';
              }
              return null;
            },
          ),
          ElevatedButton(
            onPressed: () {
              if (_formKey.currentState!.validate()) {
                FirebaseFirestore.instance.collection('tasks').add({
                  'title': _taskController.text,
                  'isDone': false,
                });
                _taskController.clear();
              }
            },
            child: Text('Add Task'),
          ),
        ],
      ),
    );
  }
}