contextmountedasyncMenengah4 mnt baca

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:

dart
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:

dart
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:

dart
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 template flutter_lints) otomatis memperingatkan setiap pemakaian context setelah await tanpa cek. Jangan di-suppress pakai // ignore, perbaiki kodenya.

Edge Case: Context yang Disimpan

Jangan pernah menyimpan BuildContext di field lalu memakainya nanti:

dart
// 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 onPressed yang langsung Navigator.push tanpa await sebelumnya.
  • Di dalam initState: widget pasti mounted, tapi context di initState belum bisa dipakai untuk Theme.of/Navigator.of yang butuh ancestor selesai dibangun. Untuk itu pakai didChangeDependencies atau WidgetsBinding.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?