BuildContext & mounted
Memahami context, async gap, dan kenapa mounted wajib dicek.
Apa itu BuildContext?
BuildContext adalah "alamat" widget di dalam pohon widget (widget tree). Flutter memakai alamat ini untuk mencari hal-hal di atas widget tersebut: tema (Theme.of(context)), lebar layar (MediaQuery.of(context)), sampai navigasi (Navigator.of(context)).
Setiap widget punya context sendiri, dan context hanya valid selama widget-nya masih terpasang di pohon (mounted).
Async Gap: Jebakan Klasik
Masalah muncul saat ada await di tengah jalan. Lihat kode ini:
onPressed: () async {
final data = await ambilData(); // user bisa menekan back DI SINI
Navigator.push(context, ...); // context mungkin sudah mati
}Antara await dan baris berikutnya ada jeda waktu (async gap). Dalam jeda itu user bisa menekan tombol back, sehingga widget di-dispose dan context-nya mati. Memakai context yang sudah mati akan melempar error seperti:
Looking up a deactivated widget's ancestor is unsafe.
Error ini adalah crash klasik aplikasi Flutter yang datanya diambil dari internet.
Solusi: Cek mounted Setelah await
Setiap State punya properti mounted yang bernilai false begitu widget di-dispose. Pola amannya:
onPressed: () async {
final data = await ambilData();
if (!mounted) return; // widget sudah mati, berhenti di sini
Navigator.push(context, MaterialPageRoute(
builder: (_) => HalamanHasil(data: data),
));
}Aturan praktisnya: setiap ada await, cek mounted sebelum memakai context atau memanggil setState di baris berikutnya.
Contoh lengkap dengan setState:
Future<void> _muat() async {
final hasil = await api.getProfil();
if (!mounted) return;
setState(() => _profil = hasil);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Profil dimuat')),
);
}Catatan teknis: Lint
use_build_context_synchronously(aktif di templateflutter_lints) otomatis memperingatkan setiap pemakaian context setelahawaittanpa cek. Jangan di-suppress pakai// ignore, perbaiki kodenya.
Edge Case: Context yang Disimpan
Jangan pernah menyimpan BuildContext di field lalu memakainya nanti:
// JANGAN:
late BuildContext _ctx;
@override
Widget build(BuildContext context) {
_ctx = context; // bisa basi setelah rebuild
...
}Context yang disimpan bisa menjadi basi (stale) setelah widget rebuild atau berpindah posisi di pohon. Selalu pakai context yang segar dari parameter build atau dari callback widget.
Pengecualian wajar: meneruskan context ke fungsi helper dalam satu frame yang sama (misalnya void _tampilDialog(BuildContext context) dipanggil langsung dari build) aman, karena tidak ada async gap.
Kapan TIDAK Perlu Cek mounted
- Kode sinkron penuh tanpa
await: tidak ada jeda, widget tidak mungkin mati di tengah jalan. - Callback yang dijamin berjalan saat widget hidup, misalnya
onPressedyang langsungNavigator.pushtanpaawaitsebelumnya. - Di dalam
initState: widget pasti mounted, tapicontextdiinitStatebelum bisa dipakai untukTheme.of/Navigator.ofyang butuh ancestor selesai dibangun. Untuk itu pakaididChangeDependenciesatauWidgetsBinding.instance.addPostFrameCallback.
Tantangan
Amankan async gap
Perbaiki kode berikut agar aman: tombol memanggil await Future.delayed(3 detik) lalu Navigator.push. Tambahkan pengecekan mounted yang benar.
Kuis Bab
Uji pemahamanmu: State & Lifecycle
Jawab 5 soal berikut, lalu tekan "Periksa Jawaban".
1.Apa yang terjadi saat setState dipanggil?
2.Kapan initState dipanggil?
3.Kenapa controller harus di-dispose?
4.Apa fungsi Key pada widget?
5.Kenapa harus cek mounted setelah await sebelum memakai context?