Comprendre l’Architecture MVVM dans Flutter
Comprendre l’Architecture MVVM dans Flutter
-
Objectif
- Comprendre les Fondamentaux de l’Architecture MVVM
- Appliquer MVVM dans un Projet Flutter
- Créer une Application Modulaire et Testable
- Acquérir des Pratiques pour des Applications de Grande Échelle
-
Présentation
- Le développement d’applications mobiles modernes nécessite une architecture solide pour assurer la maintenabilité, la testabilité et la scalabilité du code.
- MVVM (Model-View-ViewModel) est un modèle architectural utilisé dans le développement de logiciels, notamment dans Flutter, pour séparer les préoccupations d’une application en trois composants distincts :
Model
,View
etViewModel
. Cette séparation des préoccupations permet de créer une base de code bien organisée et maintenable. - Le modèle
MVVM
est une variante du modèle de conception classique Modèle-Vue-Contrôleur (MVC) et est principalement utilisé pour créer des interfaces utilisateur. - Le modèle Modèle-Vue-VueModel (MVVM) sépare l’interface utilisateur d’une application en trois composants distincts : le modèle, la vue (View) et le ViewModel. Chaque élément joue un rôle spécifique dans l’architecture globale de l’application.
-
Model
(Modèle) - Le modèle représente les données et la logique métier de l’application.
- Il est responsable du stockage, de la récupération et de la manipulation des données dont l’application a besoin pour fonctionner.
- Le modèle doit être complètement indépendant de la vue et du
ViewModel
, et ne doit contenir que la logique liée aux données elles-mêmes. - Le modèle dans l’architecture MVVM est le cœur de l’application. Il est responsable de la gestion de toute la logique métier et de la gestion des données. Le modèle comprend toutes les classes de modèle, toutes les classes de référentiel et toutes les classes utilitaires.
- Les classes utilitaires fournissent des fonctions d’assistance utilisées dans l’ensemble de l’application. Il peut s’agir de fonctions de formatage de date, de manipulation de chaînes ou de vérifications de connectivité réseau.
-
View
(Vue) - La vue dans l’architecture MVVM est responsable de l’affichage de l’interface utilisateur et de la capture de tous les événements utilisateur. Elle comprend toutes les classes liées à la vue, comme la classe HomeScreen. La vue interagit avec le ViewModel pour gérer les événements utilisateur et mettre à jour l’interface utilisateur.
- La vue est généralement implémentée sous forme d’arborescence de widgets dans Flutter. Elle utilise le contexte BuildContext pour interagir avec le ViewModel et mettre à jour l’interface utilisateur en fonction des données qu’elle reçoit. La vue est également chargée de capturer les événements utilisateur, comme les clics sur les boutons ou la saisie de texte, et de les transmettre au ViewModel.
-
ViewModel
- Le
ViewModel
est le pont entre le modèle et la vue. Il comprend toutes les classes liées au ViewModel qui gèrent les demandes d’événements utilisateur et mettent à jour la vue. Le ViewModel utilise le contexte BuildContext pour interagir avec le modèle et la vue. - Le
ViewModel
est responsable de la gestion de tous les événements utilisateur et de la logique métier au sein de l’application. Il interagit avec le modèle pour traiter les demandes d’événements utilisateur et mettre à jour la vue. Le ViewModel avertit également la vue lorsque les données changent, ce qui permet à l’interface utilisateur de se mettre à jour automatiquement. -
Rôle du ViewModel dans le MVVM
- Préparer les données : Le ViewModel reçoit les données du modèle sous leur forme brute (par exemple, des objets JSON d’une API) et les transforme au besoin. Par exemple, il peut convertir une date en un format plus lisible ou calculer des valeurs dérivées à partir des données reçues.
- Notifier la vue : Dans une architecture réactive, comme avec Provider ou Riverpod, le ViewModel informe la vue de tout changement d’état (ajout, suppression, ou modification de données). Cela garantit que la vue reste synchronisée avec les données du modèle.
- Gérer les interactions utilisateur : Le ViewModel réagit aux actions utilisateur (clics, saisies, etc.) et met à jour les données en conséquence. Par exemple, si l’utilisateur effectue une recherche, le ViewModel récupère les résultats et met à jour la vue.
-
Solutions de Gestion d’État Courantes pour MVVM dans Flutter
- Voici un aperçu de quelques solutions populaires en Flutter :
- Provider : Solution native à Flutter et recommandée par l’équipe Flutter, Provider est léger, simple à implémenter, et bien intégré avec les widgets Flutter pour la gestion d’état dans MVVM.
- BLoC (Business Logic Component) : Fondé sur le modèle de flux (streams) et de réactivité, BLoC utilise les Streams pour gérer l’état. Idéal pour des applications qui nécessitent une séparation stricte des couches et une gestion asynchrone.
- Riverpod : Une amélioration de Provider, avec une syntaxe plus claire et des fonctionnalités avancées (comme la détection de code mort). Riverpod est idéal pour une gestion d’état complexe, tout en restant simple et intuitif.
- GetX : Solution rapide et complète pour la gestion de l’état, la navigation et les dépendances. GetX est simple à utiliser, mais est moins prescriptif quant à la structuration en couches.
- En fonction de la complexité et des besoins de l’application, chaque solution peut être utilisée pour organiser et structurer le ViewModel, apportant ainsi modularité et réactivité à votre projet Flutter.
-
Utiliser le modèle MVVM avec Sqflite dans une application Flutter,
- Pour utiliser le modèle MVVM avec
Sqflite
dans une application Flutter, vous devez structurer votre code en plusieurs classes standard qui représentent les différentes couches de l’architecture. - Voici un aperçu des classes typiques que vous devriez déclarer :
-
Modèle (Model)
- Cette classe représente la structure de vos données et peut inclure des méthodes pour convertir entre des objets et des formats de données (comme des maps pour la base de données).
- Elle sert uniquement à définir les attributs et les méthodes de transformation des données, par exemple, pour convertir en et depuis un Map.
-
Service de Base de Données
- Cette classe gère toutes les opérations liées à la base de données, telles que l’insertion, la mise à jour et la récupération des données.
-
ViewModel
- Cette classe interagit avec le modèle et le service de base de données. Elle contient la logique métier et notifie les vues des changements d’état.
- _items = [];
List
- get items => _items; Future
fetchItems() async { _items = await _databaseService.getItems(); notifyListeners(); } Future addItem(Item item) async { await _databaseService.insertItem(item); await fetchItems(); // Récupérer les éléments après ajout } } - get items => _items; Future
-
Vue (View)
- Les widgets Flutter qui affichent les données et interagissent avec l’utilisateur. Ils écoutent les changements dans le ViewModel.
-
Main App
- Enfin, configurez votre application pour utiliser Provider et initialiser le ViewModel.
-
Conclusion
- Cette structure de classes vous permet de suivre le modèle MVVM tout en utilisant Sqflite pour la persistance des données. Assurez-vous d’adapter et d’étendre ces classes selon les besoins spécifiques de votre application.
Le modèle représente les données et la logique métier de l’application. Il encapsule les structures de données, les interactions de base de données et toute autre logique d’application principale. Dans Flutter, le modèle peut inclure des classes ou des modèles de données qui définissent le schéma de données, ainsi que des services permettant de récupérer ou de manipuler des données.
Dans l’
architecture MVVM
, le ViewModel sert effectivement de pont entre le modèle (Model) et la vue (View). Il extrait, transforme, et prépare les données issues du modèle pour qu’elles soient directement utilisables par la vue. En Flutter, cela est rendu possible grâce aux solutions de gestion d’état telles que Provider, BLoC, Riverpod, GetX, etc., chacune offrant ses avantages pour structurer l’interaction entre les différentes couches de l’application.
class Item {
final int id;
final String name;
Item({required this.id, required this.name});
// Convertir un objet Item en Map pour Sqflite
Map toMap() {
return {
'id': id,
'name': name,
};
}
// Créer un objet Item à partir d'un Map
factory Item.fromMap(Map map) {
return Item(
id: map['id'],
name: map['name'],
);
}
}
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DatabaseService {
static final DatabaseService _instance = DatabaseService._internal();
Database? _database;
factory DatabaseService() {
return _instance;
}
DatabaseService._internal();
Future get database async {
if (_database != null) return _database!;
_database = await _initDatabase();
return _database!;
}
Future _initDatabase() async {
String path = join(await getDatabasesPath(), 'items.db');
return await openDatabase(path, version: 1, onCreate: (db, version) {
return db.execute(
'CREATE TABLE items(id INTEGER PRIMARY KEY, name TEXT)',
);
});
}
Future insertItem(Item item) async {
final db = await database;
await db.insert('items', item.toMap());
}
Future> getItems() async {
final db = await database;
final List
import 'package:flutter/foundation.dart';
class ItemViewModel with ChangeNotifier {
final DatabaseService _databaseService = DatabaseService();
List
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ItemListView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final itemViewModel = Provider.of(context);
return Scaffold(
appBar: AppBar(title: Text('Items')),
body: FutureBuilder(
future: itemViewModel.fetchItems(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
return ListView.builder(
itemCount: itemViewModel.items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(itemViewModel.items[index].name),
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Ajouter un nouvel élément
itemViewModel.addItem(Item(id: 0, name: 'New Item'));
},
child: Icon(Icons.add),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => ItemViewModel(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MVVM avec Sqflite',
home: ItemListView(),
);
}
}