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
- Model Todo: class dengan
id(string unik),judul, danselesai(bool), plustoJson()danfromJson(). Id unik penting, jangan pakai index list karena bergeser saat item dihapus. - Fungsi simpan/muat: encode list ke JSON string, simpan dengan satu key di SharedPreferences. Muat sekali saat aplikasi dibuka.
- UI tambah: TextField plus tombol, tolak input kosong (atau hanya spasi) dengan validasi.
- UI list:
CheckboxListTileper item, judul item selesai tampil tercoret (TextDecoration.lineThrough). Swipe atau tombol untuk hapus. - Filter:
SegmentedButtondengan tiga pilihan: Semua, Aktif, Selesai. List yang tampil adalah hasil filter, bukan list asli. - Statistik: teks "3 dari 10 selesai" plus
LinearProgressIndicatordengan value selesai/total.
Kode Kunci: Serialisasi dan Filter
Model dengan serialisasi JSON yang defensif:
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:
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?