Menu
CoddyTech

Daftar Tugas: Batalkan Penghapusan

Termasuk bagian Proyek dalam perjalanan Flutter di Coddy. Pelajaran 7 dari 36.

Geseran berlangsung cepat, begitu juga geseran yang tidak disengaja. Aplikasi yang baik memungkinkan kamu membatalkannya: pesan di bagian bawah memberi tahu apa yang dihapus, dengan tombol Undo di sampingnya.

SnackBar menerima action:, yaitu SnackBarAction dengan label dan onPressed. Untuk mengembalikan tugas ke posisi semula, ingat posisinya sebelum kamu menghapusnya:

final index = _tasks.indexOf(task);
// hapus tugasnya, lalu tampilkan SnackBar
SnackBarAction(
  label: 'Undo',
  onPressed: () {
    setState(() {
      _tasks.insert(index, task);
    });
  },
)

Jika kamu menggeser dua tugas dengan cepat, pesan kedua menunggu sampai waktu pesan pertama habis. Panggil ScaffoldMessenger.of(context).hideCurrentSnackBar(); sebelum showSnackBar, agar pesan terbaru langsung muncul.

insert(index, task) mengembalikan tugas ke posisi tersebut dan menggeser tugas-tugas setelahnya ke bawah. add akan menambahkannya di akhir.

Fungsi yang kamu berikan ke onPressed mengingat index dan task dari saat kamu membuatnya, meskipun fungsi itu dijalankan nanti. Itulah closure, dan callback mengandalkannya setiap saat.

challenge icon

Tantangan

Sedang

Buat agar penghapusan bisa dibatalkan. Di _deleteTask:

  • sebelum menghapus tugas, simpan posisinya dengan indexOf
  • setelah menghapusnya, tampilkan SnackBar dengan teks Deleted dan judul tugas, seperti Deleted Walk the dog
  • tambahkan aksi Undo yang mengembalikan tugas ke posisi semula
  • sembunyikan pesan yang sedang ditampilkan terlebih dahulu, agar penghapusan baru menggantikannya

Coba sendiri

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 iconUji dirimu

Pelajaran ini berisi kuis singkat. Mulai pelajarannya untuk menjawab kuis dan melacak progresmu.

Semua pelajaran di Proyek

Latihan sendiri: Compiler Flutter online