Proyek Akhir: Aplikasi Daftar Tugas

Menggabungkan enum, class immutable, sealed result, collection, JSON, async, dan penanganan error menjadi aplikasi konsol Daftar Tugas; lalu langkah belajar berikutnya menuju Flutter.

Part 20 dari 20 ± 70 menit Gratis

Yang akan kita bangun

Aplikasi konsol Daftar Tugas: menambah tugas dengan prioritas, menandai selesai, memfilter, menyimpan ke berkas JSON, dan menampilkan ringkasan. Proyek ini memakai hampir semua yang sudah dipelajari: enum, class, collection, null safety, record, pattern matching, exception, async, dan JSON.

Rancangan

BagianPeran
enum Prioritasrendah, sedang, tinggi (dengan label)
class TugasData satu tugas (immutable), copyWith, toJson/fromJson
class DaftarTugasLogika: tambah, selesai, filter, ringkasan
class PenyimpananBaca/tulis berkas JSON (async)
mainMenjalankan skenario atau menu

Langkah 1: enum Prioritas

enum Prioritas {
  rendah('Rendah'),
  sedang('Sedang'),
  tinggi('Tinggi');

  final String label;
  const Prioritas(this.label);

  static Prioritas dariNama(String? nama) =>
      values.firstWhere((p) => p.name == nama, orElse: () => sedang);
}

Langkah 2: class Tugas yang immutable

class Tugas {
  final int id;
  final String judul;
  final Prioritas prioritas;
  final bool selesai;

  const Tugas({
    required this.id,
    required this.judul,
    this.prioritas = Prioritas.sedang,
    this.selesai = false,
  });

  Tugas copyWith({bool? selesai}) =>
      Tugas(id: id, judul: judul, prioritas: prioritas, selesai: selesai ?? this.selesai);

  Map<String, dynamic> toJson() => {
        'id': id, 'judul': judul, 'prioritas': prioritas.name, 'selesai': selesai,
      };

  factory Tugas.fromJson(Map<String, dynamic> j) => Tugas(
        id: j['id'] as int,
        judul: j['judul'] as String,
        prioritas: Prioritas.dariNama(j['prioritas'] as String?),
        selesai: j['selesai'] == true,
      );

  @override
  String toString() => '[${selesai ? 'x' : ' '}] #$id $judul (${prioritas.label})';
}

Karena Tugas immutable, "mengubah" berarti membuat salinan baru dengan copyWith. Pola ini membuat data lebih mudah dipahami dan bebas bug perubahan tak terduga.

Langkah 3: DaftarTugas dan hasil operasi

sealed class Hasil {}
class Berhasil extends Hasil { final Tugas tugas; Berhasil(this.tugas); }
class Gagal extends Hasil { final String pesan; Gagal(this.pesan); }

class DaftarTugas {
  final List<Tugas> _isi = [];
  int _idBerikut = 1;

  List<Tugas> get semua => List.unmodifiable(_isi);

  Hasil tambah(String judul, {Prioritas prioritas = Prioritas.sedang}) {
    if (judul.trim().isEmpty) return Gagal('Judul tidak boleh kosong');
    var t = Tugas(id: _idBerikut++, judul: judul.trim(), prioritas: prioritas);
    _isi.add(t);
    return Berhasil(t);
  }

  Hasil selesaikan(int id) {
    var i = _isi.indexWhere((t) => t.id == id);
    if (i == -1) return Gagal('Tugas #$id tidak ditemukan');
    _isi[i] = _isi[i].copyWith(selesai: true);
    return Berhasil(_isi[i]);
  }

  List<Tugas> belumSelesai() => _isi.where((t) => !t.selesai).toList();

  (int selesai, int total) ringkasan() =>
      (_isi.where((t) => t.selesai).length, _isi.length);
}

Langkah 4: Penyimpanan JSON

class Penyimpanan {
  final File berkas;
  Penyimpanan(String path) : berkas = File(path);

  Future<void> simpan(List<Tugas> daftar) async {
    var teks = JsonEncoder.withIndent('  ').convert(daftar.map((t) => t.toJson()).toList());
    await berkas.writeAsString(teks);
  }

  Future<List<Tugas>> muat() async {
    if (!await berkas.exists()) return [];
    try {
      var data = jsonDecode(await berkas.readAsString()) as List<dynamic>;
      return data.map((e) => Tugas.fromJson(e as Map<String, dynamic>)).toList();
    } on FormatException {
      return [];     // berkas rusak: mulai dari kosong
    }
  }
}

Langkah 5: Menyatukan di main

Future<void> main() async {
  var daftar = DaftarTugas();

  daftar.tambah('Belajar Dart', prioritas: Prioritas.tinggi);
  daftar.tambah('Membuat proyek akhir');
  var hasil = daftar.tambah('   ');                  // ditolak

  print(switch (hasil) {
    Berhasil(tugas: var t) => 'Ditambah: $t',
    Gagal(pesan: var p) => 'Gagal: $p',
  });

  daftar.selesaikan(1);
  daftar.semua.forEach(print);

  var (selesai, total) = daftar.ringkasan();
  print('Selesai $selesai dari $total tugas');

  await Penyimpanan('tugas.json').simpan(daftar.semua);
}

Menambah menu interaktif

while (true) {
  stdout.write('\n1) Tambah  2) Selesai  3) Daftar  0) Keluar : ');
  switch (stdin.readLineSync()?.trim()) {
    case '1':
      stdout.write('Judul: ');
      daftar.tambah(stdin.readLineSync() ?? '');
    case '2':
      stdout.write('ID: ');
      daftar.selesaikan(int.tryParse(stdin.readLineSync() ?? '') ?? -1);
    case '3':
      daftar.semua.forEach(print);
    case '0':
      return;
    default:
      print('Pilihan tidak dikenal');
  }
}

Tantangan lanjutan

  • Tambahkan fitur hapus tugas dan edit judul.
  • Urutkan tampilan berdasarkan prioritas (sort dengan compareTo pada bobot).
  • Tambahkan tenggat waktu (DateTime) dan tampilkan tugas yang terlambat.
  • Tulis tes untuk DaftarTugas memakai package:test.
  • Gunakan paket args agar bisa dipakai seperti tugas tambah "judul".

Selamat, kamu sudah menguasai dasar Dart!

Kamu kini paham: tipe data, null safety, function, collection, OOP lengkap, fitur Dart 3 (record, pattern, sealed), exception, async, JSON, paket, dan testing. Langkah berikutnya:

  • Flutter — bangun aplikasi mobile dengan Dart. Pelajari course Pengenalan Flutter di KelasIT; semua materi Dart di sini langsung terpakai.
  • Dart di server — framework shelf atau dart_frog.
  • Baca Effective Dart di dart.dev untuk gaya kode yang baik.

Selesaikan kuis di akhir course untuk meraih sertifikat. Semangat!

Praktikkan langsung, gratis

Tulis dan jalankan kode langsung di browser, dapatkan penilaian otomatis. Daftar gratis kapan saja untuk menyimpan progres dan meraih sertifikat.

Coba latihan sekarang, tanpa daftar
Bagikan: WhatsApp Facebook LinkedIn X