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.
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
| Bagian | Peran |
|---|---|
enum Prioritas | rendah, sedang, tinggi (dengan label) |
class Tugas | Data satu tugas (immutable), copyWith, toJson/fromJson |
class DaftarTugas | Logika: tambah, selesai, filter, ringkasan |
class Penyimpanan | Baca/tulis berkas JSON (async) |
main | Menjalankan 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 (
sortdengancompareTopadabobot). - Tambahkan tenggat waktu (
DateTime) dan tampilkan tugas yang terlambat. - Tulis tes untuk
DaftarTugasmemakaipackage:test. - Gunakan paket
argsagar bisa dipakai sepertitugas 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
shelfataudart_frog. - Baca Effective Dart di dart.dev untuk gaya kode yang baik.
Selesaikan kuis di akhir course untuk meraih sertifikat. Semangat!