Introduction
Flutter est un framework populaire pour le développement d’applications mobiles multiplateformes. L’un des défis majeurs lors du développement d’une application est la gestion de l’état. L’utilisation d’un StreamController
et du Bloc Pattern
est une approche efficace pour gérer les flux de données en temps réel.
Ce tutoriel explique comment implémenter un Bloc
en utilisant un StreamController
pour gérer une application simple de gestion de tâches (Todo List), incluant la persistance des données avec localstorage
.
Prérequis
Avant de commencer, assurez-vous d’avoir :
- Flutter installé sur votre machine
- Un éditeur comme Visual Studio Code ou Android Studio
- Une connaissance de base en Flutter et Dart
Bloc avec StreamController vs flutter_bloc
Dans ce tutoriel, nous utilisons directement StreamController
pour gérer l’état, ce qui est une approche plus légère et simple. Cependant, Flutter propose également une bibliothèque nommée flutter_bloc
qui suit un modèle basé sur les événements et états.
Différences principales :
StreamController | flutter_bloc |
---|---|
Plus simple et léger | Plus structuré et robuste |
Utilise directement des StreamController | Utilise une architecture basée sur des événements et des états |
Moins de code boilerplate | Nécessite plus de configuration |
Idéal pour les petites applications | Idéal pour les applications complexes |
Si vous souhaitez une gestion d’état plus avancée avec une meilleure séparation des responsabilités, flutter_bloc
est une bonne alternative. Sinon, StreamController
est suffisant pour des applications plus légères.
Installation des dépendances
Ajoutez les dépendances localstorage
dans le fichier pubspec.yaml
:
dependencies:
flutter:
sdk: flutter
localstorage: ^4.0.0
Puis exécutez la commande :
flutter pub get
Implémentation du Bloc avec StreamController
1. Créer le modèle de données
Créons un fichier todo_model.dart
pour définir notre modèle de tâche :
class TodoModel {
final String task;
final bool isCompleted;
TodoModel({required this.task, this.isCompleted = false});
// Convert object to JSON
Map<String, dynamic> toJson() => {
'task': task,
'isCompleted': isCompleted,
};
// Convert JSON to object
factory TodoModel.fromJson(Map<String, dynamic> json) => TodoModel(
task: json['task'],
isCompleted: json['isCompleted'],
);
}
2. Créer le Bloc avec StreamController
Créons un fichier todo_bloc.dart
:
import 'dart:async';
import 'package:localstorage/localstorage.dart';
import 'todo_model.dart';
class TodoBloc {
final LocalStorage storage = LocalStorage('todo_app');
List<TodoModel> _todos = [];
// StreamController for managing the todo list state
final StreamController<List<TodoModel>> _streamController = StreamController<List<TodoModel>>.broadcast();
// Sink for adding new states
Sink<List<TodoModel>> get sink => _streamController.sink;
// Stream for listening to updates
Stream<List<TodoModel>> get stream => _streamController.stream;
// Load saved todos from local storage
Future<void> loadTodos() async {
await storage.ready;
List<dynamic>? storedTodos = storage.getItem('todos');
_todos = storedTodos?.map((e) => TodoModel.fromJson(e)).toList() ?? [];
sink.add(_todos);
}
// Add a new todo
void addTodo(String task) {
_todos.add(TodoModel(task: task));
storage.setItem('todos', _todos.map((e) => e.toJson()).toList());
sink.add(_todos);
}
// Remove a todo by index
void removeTodo(int index) {
_todos.removeAt(index);
storage.setItem('todos', _todos.map((e) => e.toJson()).toList());
sink.add(_todos);
}
// Dispose method to close the StreamController
void dispose() {
_streamController.close();
}
}
// Create a singleton instance of the bloc
final todoBloc = TodoBloc();
3. Intégrer le Bloc dans l’UI
Dans le fichier main.dart
, utilisez un StreamBuilder
pour écouter les mises à jour :
import 'package:flutter/material.dart';
import 'todo_bloc.dart';
import 'todo_model.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: TodoScreen(),
);
}
}
class TodoScreen extends StatefulWidget {
@override
_TodoScreenState createState() => _TodoScreenState();
}
class _TodoScreenState extends State<TodoScreen> {
final TextEditingController _controller = TextEditingController();
@override
void initState() {
super.initState();
todoBloc.loadTodos();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Todo List')),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _controller,
decoration: InputDecoration(labelText: 'Enter task'),
),
),
ElevatedButton(
onPressed: () {
if (_controller.text.isNotEmpty) {
todoBloc.addTodo(_controller.text);
_controller.clear();
}
},
child: Text('Add Todo'),
),
Expanded(
child: StreamBuilder<List<TodoModel>>(
stream: todoBloc.stream,
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No tasks available'));
}
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data![index].task),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => todoBloc.removeTodo(index),
),
);
},
);
},
),
),
],
),
);
}
}
Conclusion
En suivant ce tutoriel, vous avez appris à :
- Gérer l’état d’une liste de tâches avec un
StreamController
- Ajouter la persistance des données avec
localstorage
- Utiliser un
StreamBuilder
pour gérer l’affichage dynamique - Comprendre la différence entre
StreamController
etflutter_bloc
Cette approche est efficace pour des applications nécessitant des mises à jour en temps réel. Vous pouvez améliorer cette architecture en ajoutant la gestion des tâches complétées ou en intégrant flutter_bloc
pour un projet plus complexe.