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'),
),
],
),
);
}
}