To-do List: Undo a Delete
Part of the Projects section of Coddy's Flutter journey. Lesson 7 of 36.
A swipe is quick, and so is a swipe by mistake. Good apps let you take it back: a message at the bottom says what was deleted, with an Undo button beside it.
A SnackBar takes an action:, a SnackBarAction with a label and an onPressed. To put the task back where it was, remember its position before you remove it:
final index = _tasks.indexOf(task);
// remove it, then show the SnackBar
SnackBarAction(
label: 'Undo',
onPressed: () {
setState(() {
_tasks.insert(index, task);
});
},
)Swipe two tasks quickly and the second message waits until the first one times out. Call ScaffoldMessenger.of(context).hideCurrentSnackBar(); before showSnackBar, so the newest message shows at once.
insert(index, task) puts the task back at that position and moves the ones after it down. add would put it at the end.
The function you hand to onPressed remembers index and task from the moment you made it, even though it runs later. That is a closure, and callbacks rely on it all the time.
Challenge
MediumLet a delete be undone. In _deleteTask:
- before removing the task, keep its position with
indexOf - after removing it, show a
SnackBarwith the textDeletedand the task's title, likeDeleted Walk the dog - give it an
Undoaction that puts the task back at its old position - hide the message that is showing first, so a new delete replaces it
Try it yourself
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,
),
),
);
},
),
),
],
),
);
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Projects
Practice on your own: Online Flutter compiler