Menu
CoddyTech

Liste de tâches : Annuler une suppression

Fait partie de la section Projets du Journey Flutter de Coddy. Leçon 7 sur 36.

Un balayage est rapide, et un balayage accidentel aussi. Les bonnes applications vous permettent de revenir en arrière : un message en bas indique ce qui a été supprimé, avec un bouton Annuler à côté.

Un SnackBar prend un action:, un SnackBarAction avec un label et un onPressed. Pour remettre la tâche à sa place, mémorisez sa position avant de la supprimer :

final index = _tasks.indexOf(task);
// supprime-le, puis affiche la SnackBar
SnackBarAction(
  label: 'Undo',
  onPressed: () {
    setState(() {
      _tasks.insert(index, task);
    });
  },
)

Si vous balayez rapidement deux tâches, le deuxième message attend que le premier disparaisse. Appelez ScaffoldMessenger.of(context).hideCurrentSnackBar(); avant showSnackBar, afin que le message le plus récent s’affiche immédiatement.

insert(index, task) remet la tâche à cette position et décale vers le bas celles qui la suivent. add la placerait à la fin.

La fonction que vous transmettez à onPressed mémorise index et task tels qu’ils étaient au moment où vous l’avez créée, même si elle s’exécute plus tard. C’est une fermeture, sur laquelle les fonctions de rappel s’appuient constamment.

challenge icon

Défi

Moyen

Permettez d’annuler une suppression. Dans _deleteTask :

  • avant de supprimer la tâche, conservez sa position avec indexOf
  • après l’avoir supprimée, affichez une SnackBar avec le texte Deleted et le titre de la tâche, comme Deleted Walk the dog
  • ajoutez une action Undo qui remet la tâche à son ancienne position
  • masquez d’abord le message affiché, afin qu’une nouvelle suppression le remplace

Essayez vous-même

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
      ),
      home: const TodoScreen(),
    );
  }
}

class Task {
  Task(this.title);

  final String title;
  bool done = false;
}

class TodoScreen extends StatefulWidget {
  const TodoScreen({super.key});

  @override
  State<TodoScreen> createState() => _TodoScreenState();
}

class _TodoScreenState extends State<TodoScreen> {
  final List<Task> _tasks = [Task('Buy milk'), Task('Walk the dog'), Task('Read a book')];
  final _controller = TextEditingController();

  int get _left => _tasks.where((task) => !task.done).length;

  void _addTask() {
    final title = _controller.text.trim();
    if (title.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Type a task first')),
      );
      return;
    }
    setState(() {
      _tasks.add(Task(title));
    });
    _controller.clear();
  }

  void _deleteTask(Task task) {
    setState(() {
      _tasks.remove(task);
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('My Tasks')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _controller,
                    decoration: const InputDecoration(
                      labelText: 'New task',
                      border: OutlineInputBorder(),
                    ),
                  ),
                ),
                const SizedBox(width: 8),
                IconButton.filled(
                  onPressed: _addTask,
                  icon: const Icon(Icons.add),
                ),
              ],
            ),
          ),
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            child: Row(
              children: [
                Text(
                  _left == 1 ? '1 task left' : '$_left tasks left',
                  style: Theme.of(context).textTheme.titleSmall,
                ),
              ],
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: _tasks.length,
              itemBuilder: (context, index) {
                final task = _tasks[index];
                return Dismissible(
                  key: ObjectKey(task),
                  direction: DismissDirection.endToStart,
                  background: Container(
                    color: Colors.red,
                    alignment: Alignment.centerRight,
                    padding: const EdgeInsets.symmetric(horizontal: 24),
                    child: const Icon(Icons.delete, color: Colors.white),
                  ),
                  onDismissed: (direction) => _deleteTask(task),
                  child: ListTile(
                    leading: Checkbox(
                      value: task.done,
                      onChanged: (value) {
                        setState(() {
                          task.done = value!;
                        });
                      },
                    ),
                    title: Text(
                      task.title,
                      style: task.done
                          ? const TextStyle(
                              decoration: TextDecoration.lineThrough,
                              color: Colors.grey,
                            )
                          : null,
                    ),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Projets

Entraînez-vous par vous-même : Compilateur Flutter en ligne