proyektodoaplikasiMahir5 mnt baca

Proyek Mini: Todo + SharedPreferences

Aplikasi todo persisten dengan filter dan statistik.

Tujuan Proyek: Todo Persisten Tanpa Database Berat

Tidak semua data butuh SQLite. Daftar todo adalah contoh sempurna data kecil dan sederhana: list pendek, struktur datar, tidak butuh query kompleks. Di sinilah SharedPreferences bersinar, penyimpanan key-value bawaan yang super ringan. Proyek ini mengajarkan pola yang sangat umum di aplikasi nyata: serialisasi objek ke JSON string, simpan sebagai satu value, baca kembali saat aplikasi dibuka. Plus dua fitur yang melatih logika UI: filter (semua, aktif, selesai) dan statistik progres dengan progress bar.

Langkah Build, Satu per Satu

  1. Model Todo: class dengan id (string unik), judul, dan selesai (bool), plus toJson() dan fromJson(). Id unik penting, jangan pakai index list karena bergeser saat item dihapus.
  2. Fungsi simpan/muat: encode list ke JSON string, simpan dengan satu key di SharedPreferences. Muat sekali saat aplikasi dibuka.
  3. UI tambah: TextField plus tombol, tolak input kosong (atau hanya spasi) dengan validasi.
  4. UI list: CheckboxListTile per item, judul item selesai tampil tercoret (TextDecoration.lineThrough). Swipe atau tombol untuk hapus.
  5. Filter: SegmentedButton dengan tiga pilihan: Semua, Aktif, Selesai. List yang tampil adalah hasil filter, bukan list asli.
  6. Statistik: teks "3 dari 10 selesai" plus LinearProgressIndicator dengan value selesai/total.

Kode Kunci: Serialisasi dan Filter

Model dengan serialisasi JSON yang defensif:

dart
class Todo {
  final String id;
  final String judul;
  final bool selesai;

  const Todo({required this.id, required this.judul, this.selesai = false});

  Map<String, Object?> toJson() =>
      {'id': id, 'judul': judul, 'selesai': selesai};

  factory Todo.fromJson(Map<String, dynamic> json) => Todo(
        id: json['id'] as String? ?? '',
        judul: json['judul'] as String? ?? '(tanpa judul)',
        selesai: json['selesai'] as bool? ?? false,
      );

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

Simpan dan muat seluruh list sebagai satu string:

dart
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';

Future<void> simpan(List<Todo> todos) async {
  final prefs = await SharedPreferences.getInstance();
  final data = jsonEncode(todos.map((t) => t.toJson()).toList());
  await prefs.setString('todos', data);
}

Future<List<Todo>> muat() async {
  final prefs = await SharedPreferences.getInstance();
  final s = prefs.getString('todos');
  if (s == null || s.isEmpty) return [];
  try {
    final list = jsonDecode(s) as List;
    return list.map((e) => Todo.fromJson(e as Map<String, dynamic>)).toList();
  } catch (_) {
    return []; // data korup: mulai dari kosong, jangan crash
  }
}

Perhatikan fromJson yang defensif: setiap field punya fallback supaya satu field rusak tidak meruntuhkan seluruh list. Dan muat dibungkus try-catch karena JSON korup (misalnya aplikasi mati saat menulis) tidak boleh membuat aplikasi crash saat dibuka.

Catatan teknis: Pola JSON-string cocok untuk data kecil (di bawah 100 item). Untuk data besar atau query kompleks, pakai sqflite. Jangan memaksakan SharedPreferences untuk sesuatu yang butuh database.

Edge Cases yang Wajib Ditangani

Simpan setiap kali ada mutasi (tambah, centang, hapus), jangan menunggu tombol simpan, karena pengguna mobile tidak mengenal konsep save manual. Muat data sekali di initState dengan indikator loading, jangan di build karena akan reload tiap rebuild. Filter harus dihitung dari list asli setiap kali, jangan menyimpan list hasil filter sebagai state terpisah yang bisa basi. Progress bar butuh guard pembagian nol: kalau list kosong, value-nya 0, bukan NaN. Dan validasi input harus menolak string yang hanya berisi spasi (trim().isEmpty), bukan cuma string kosong.

Kapan Pendekatan Ini Tidak Cukup

Begitu butuh pencarian teks, pengurutan kompleks, atau relasi antar data, JSON string jadi menyakitkan karena tiap query berarti decode seluruh list. Di titik itu pindah ke sqflite. SharedPreferences juga bukan tempat menyimpan data sensitif (token, password), karena tidak terenkripsi. Anggap proyek ini sebagai penguasaan pola serialisasi JSON, skill yang dipakai di mana-mana, dari REST API sampai local storage.

Tantangan

Filter todo

Diberi List<Todo> (field selesai bool), tampilkan dengan filter SegmentedButton: Semua, Aktif (belum selesai), Selesai.

Tugas

Aplikasi Todo Persisten

Bangun aplikasi todo list: tambah, tandai selesai, hapus, filter (semua/aktif/selesai), dan statistik progres. Data tersimpan persisten via SharedPreferences (format JSON). Kumpulkan sebagai project Flutter yang bisa di-build.

Kriteria penilaian:

  • Tambah, selesai, dan hapus todo berfungsi
  • Filter semua/aktif/selesai berfungsi benar
  • Data tetap ada setelah aplikasi dibuka ulang
  • Menampilkan statistik progres (x dari y selesai)

AI tutor akan memeriksa bug, kesalahan syntax, dan memberi saran perbaikan.

Kuis Bab

Uji pemahamanmu: Penyimpanan, Rilis & Proyek

Jawab 5 soal berikut, lalu tekan "Periksa Jawaban".

1.Kapan memakai SharedPreferences vs sqflite?

2.Kenapa query sqflite harus memakai whereArgs?

3.Apa itu repository pattern dalam konteks sqflite?

4.Format file apa yang wajib untuk upload ke Play Store?

5.Apa yang tidak boleh di-commit ke git saat release?