Menu
CoddyTech

To-do-Liste: Löschen rückgängig machen

Teil des Abschnitts Projekte der Flutter-Journey von Coddy. Lektion 7 von 36.

Ein Wisch ist schnell erledigt – und ein versehentlicher Wisch ebenso. Gute Apps lassen dich den Vorgang rückgängig machen: Unten erscheint eine Meldung, die angibt, was gelöscht wurde, daneben eine Schaltfläche zum Rückgängigmachen.

Ein SnackBar erhält ein action:, eine SnackBarAction mit einem label und einem onPressed. Damit die Aufgabe an ihre ursprüngliche Position zurückkommt, merke dir ihre Position, bevor du sie entfernst:

final index = _tasks.indexOf(task);
// Entferne es und zeige dann die SnackBar an
SnackBarAction(
  label: 'Undo',
  onPressed: () {
    setState(() {
      _tasks.insert(index, task);
    });
  },
)

Wenn du zwei Aufgaben schnell hintereinander wegwischst, wartet die zweite Meldung, bis die erste ausgeblendet wird. Rufe ScaffoldMessenger.of(context).hideCurrentSnackBar(); vor showSnackBar auf, damit die neueste Meldung sofort erscheint.

insert(index, task) fügt die Aufgabe an dieser Position wieder ein und verschiebt die nachfolgenden Aufgaben nach unten. add würde sie ans Ende setzen.

Die Funktion, die du an onPressed übergibst, merkt sich index und task von dem Zeitpunkt, an dem du sie erstellt hast, auch wenn sie erst später ausgeführt wird. Das nennt man Closure, und Callbacks nutzen das ständig.

challenge icon

Aufgabe

Mittel

Mache das Löschen rückgängig. In _deleteTask:

  • speichere vor dem Entfernen der Aufgabe ihre Position mit indexOf
  • zeige nach dem Entfernen eine SnackBar mit dem Text Deleted und dem Titel der Aufgabe an, zum Beispiel Deleted Walk the dog
  • füge eine Aktion Undo hinzu, die die Aufgabe an ihre alte Position zurücksetzt
  • blende zuerst die angezeigte Nachricht aus, damit ein neues Löschen sie ersetzt

Probier es selbst

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 iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Projekte

Übe selbstständig: Online-Flutter-Compiler