keysstatelistMenengah4 mnt baca

Widget Keys

Kapan butuh Key: list dinamis, reorder, dan menjaga state.

Kenapa Key Dibutuhkan?

Saat rebuild, Flutter harus mencocokkan widget baru dengan element lama di tree. Aturan defaultnya sederhana: cocokkan berdasarkan tipe dan posisi. Untuk UI statis ini cukup. Tapi begitu daftar bisa dihapus, disisipi, atau di-reorder, pencocokan posisi runtuh. Bayangkan tiga TextField berisi teks, lalu item pertama dihapus: Flutter mengira "field di posisi 0 tetap field di posisi 0", sehingga teks yang diketik user bergeser ke field yang salah. Key memberi tiap widget identitas yang bertahan melintasi perubahan posisi, jadi Flutter tahu persis element mana milik siapa.

dart
// TANPA key: hapus item pertama, teks field bergeser ke field yang salah
ListView.builder(
  itemCount: item.length,
  itemBuilder: (context, i) => TextField(
    controller: item[i].controller,
  ),
)

// DENGAN key: tiap field terikat pada datanya, bukan posisinya
ListView.builder(
  itemCount: item.length,
  itemBuilder: (context, i) => TextField(
    key: ValueKey(item[i].id),
    controller: item[i].controller,
  ),
)

ValueKey(id) membuat Flutter mencocokkan berdasarkan id data, bukan indeks. Item yang dihapus benar-benar hilang, dan field yang tersisa mempertahankan state-nya. Tanpa key, yang "hilang" selalu item terakhir secara visual, meski data yang dihapus adalah item pertama, bug klasik yang membingungkan.

Contoh Nyata: Dismissible + Reorder

Kasus klasik lain adalah daftar yang bisa digeser untuk menghapus sekaligus diurutkan ulang:

dart
ReorderableListView.builder(
  itemCount: tugas.length,
  onReorder: (lama, baru) {
    setState(() {
      var tujuan = baru;
      if (tujuan > lama) tujuan -= 1;
      final item = tugas.removeAt(lama);
      tugas.insert(tujuan, item);
    });
  },
  itemBuilder: (context, i) {
    final t = tugas[i];
    return Dismissible(
      key: ValueKey(t.id), // wajib: identitas stabil per item
      background: Container(color: Colors.red),
      onDismissed: (_) =>
          setState(() => tugas.removeWhere((e) => e.id == t.id)),
      child: ListTile(title: Text(t.nama)),
    );
  },
)

Tanpa key yang stabil, ReorderableListView bahkan menolak berjalan (ia mewajibkan key pada tiap anak langsung), dan Dismissible akan menghapus item yang salah setelah reorder. Key di sini bukan opsional, melainkan syarat.

Jenis-Jenis Key

  • ValueKey: identitas dari sebuah nilai (id, string, angka). Paling umum, pakai ini dulu.
  • ObjectKey: identitas dari identitas object itu sendiri. Berguna kalau datanya tidak punya id unik.
  • UniqueKey: identitas acak yang dibuat sekali. Cocok untuk memaksa widget "lahir baru" (misalnya mereset seluruh form), tapi jangan dipakai di dalam build karena tiap rebuild membuat identitas baru dan state selalu hilang.
  • GlobalKey: key global yang bisa mengakses State atau context dari mana saja. Contoh paling umum: validasi form dari tombol yang berada di luar form.
dart
final _formKey = GlobalKey<FormState>();

Form(
  key: _formKey,
  child: Column(children: [...]),
);

// dari mana saja, misalnya AppBar action:
void _simpan() {
  if (_formKey.currentState!.validate()) {
    _formKey.currentState!.save();
  }
}

Kapan TIDAK Pakai Key

Key bukan hiasan gratis. Key yang tidak perlu menghambat framework memakai ulang element, sehingga performa justru turun. Aturannya sederhana: pakai key hanya saat ada daftar dinamis (tambah, hapus, reorder) berisi widget stateful, atau saat butuh GlobalKey untuk akses dari luar. Jangan tempel ValueKey di semua widget "biar aman", dan jangan pernah pakai UniqueKey di dalam build. Kalau daftarmu statis dan tidak pernah berubah urutan, kamu tidak butuh key sama sekali.

Catatan teknis: GlobalKey itu mahal: ia mendaftarkan diri ke registry global dan mencegah subtree-nya dibuang dari memori. Satu atau dua GlobalKey untuk form tidak masalah, tapi puluhan GlobalKey di list panjang adalah bau kode yang harus diwaspadai.

Tantangan

Dismissible ber-key

Buat daftar item dengan Dismissible + ValueKey(item.id). Geser untuk menghapus, pastikan item yang terhapus benar.