Gestion d’état avec StreamController et persistance locale dans Flutter

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 :

StreamControllerflutter_bloc
Plus simple et légerPlus structuré et robuste
Utilise directement des StreamControllerUtilise une architecture basée sur des événements et des états
Moins de code boilerplateNécessite plus de configuration
Idéal pour les petites applicationsIdé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),
                      ),
                    );
                  },
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}
Star Download

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 et flutter_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.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *