setState Lebih Dalam
Kapan rebuild terjadi, apa yang dibangun ulang, dan jebakan umum.
Kenapa setState Ada?
Flutter memakai UI deklaratif: kamu cukup mendeskripsikan "tampilan untuk state saat ini", dan framework yang mengurus cara mengubah piksel di layar. Masalahnya, dunia nyata itu imperatif: tombol ditekan, data API datang, timer berdetak. setState adalah jembatan di antara keduanya. Kamu ubah variabel seperti biasa, lalu panggil setState, dan Flutter membangun ulang tampilan dari state terbaru. Tanpa mekanisme ini, kamu harus memegang referensi tiap Text dan Container lalu mengubahnya manual, seperti era findViewById yang melelahkan itu.
Apa yang Terjadi Saat Kamu Memanggilnya
setState tidak langsung membangun ulang widget saat itu juga. Ia hanya menandai element sebagai dirty, lalu framework menjadwalkan satu build ulang di frame berikutnya. Konsekuensinya dua hal. Pertama, memanggil setState sepuluh kali dalam satu event handler hanya menghasilkan satu rebuild, jadi tidak perlu takut memanggilnya berkali-kali. Kedua, memanggil setState di dalam build dilarang keras: build sedang berjalan, menandai dirinya dirty lagi sama dengan meminta loop tak berujung, dan Flutter akan melempar error.
int _hitung = 0;
void _tambah() {
setState(() {
_hitung++; // ubah state di dalam callback
});
}Callback di dalam setState sebaiknya hanya berisi perubahan state, bukan logika berat. Sorting sepuluh ribu item atau parsing JSON besar kerjakan dulu di luar, hasilnya yang dimasukkan ke state.
Future<void> _muatProduk() async {
setState(() => _produk = null); // tampilkan loading
final data = await api.ambilProduk(); // kerja berat di luar setState
if (!mounted) return; // pengguna mungkin sudah menutup halaman
setState(() {
_produk = data;
});
}Tiga Jebakan Paling Umum
1. setState setelah dispose. Ini crash paling klasik: kamu memanggil API, pengguna menutup halaman sebelum respons datang, lalu callback async memanggil setState pada State yang sudah mati. Solusinya selalu cek mounted sebelum setState di callback async, seperti contoh di atas.
2. Seluruh subtree ikut rebuild. setState membangun ulang seluruh subtree dari State tersebut, bukan satu widget kecil. Kalau state counter tinggal di halaman raksasa, seluruh halaman ikut dibangun ulang. Solusinya: pecah UI jadi widget kecil, dan bubuhkan const pada bagian yang statis supaya framework bisa melewatkannya.
// Hanya CounterWidget yang rebuild, HeaderRumit dilewati karena const
class HalamanBesar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const Column(
children: [
HeaderRumit(),
CounterWidget(),
],
);
}
}3. State ditaruh terlalu tinggi. Kalau state counter ditaruh di root aplikasi, tiap klik tombol membangun ulang segalanya. Aturan praktisnya: taruh state serendah mungkin, sedekat mungkin dengan widget yang memakainya.
Kapan Harus Pindah ke State Management
setState sempurna untuk state lokal satu layar: counter, isi form, tab aktif, status loading. Tapi untuk state yang dipakai banyak layar (keranjang belanja, data login, tema aplikasi), setState memaksamu mengangkat state ke ancestor tertinggi dan rebuild jadi mahal serta sulit dilacak. Di titik itu, Riverpod, Provider, atau BLoC memberi struktur yang lebih sehat: state terpusat, rebuild selektif, dan logika terpisah dari UI.
Catatan teknis: Di dalam
initStatekamu tidak perlu memanggilsetState, karena build pertama pasti berjalan setelahnya.setStatehanya valid saat State sedang mounted, yaitu antarainitStatedandispose.
Tantangan
Pecah widget counter
Refactor halaman counter-mu: pisahkan tampilan angka menjadi widget TampilanAngka (Stateless) dan tombol-tombol menjadi TombolCounter (Stateless dengan callback). State hanya tinggal di parent.