InheritedWidget
Konsep di balik Theme dan MediaQuery: data menurun ke bawah pohon.
Masalah yang Diselesaikan: Prop Drilling
Bayangkan data "nama toko" dibutuhkan oleh widget yang bersarang 8 level di bawah. Tanpa mekanisme khusus, kamu harus mengoper namaToko lewat constructor tiap level perantara, meski 7 di antaranya tidak peduli sama sekali. Ini namanya prop drilling: kode berisik, rapuh, dan setiap perubahan parameter memaksa edit berlapis. InheritedWidget memutus rantai itu: data "diumumkan" sekali di atas, lalu widget mana pun di bawahnya bisa mengambil langsung lewat context, tanpa perantara.
class InfoToko extends InheritedWidget {
final String namaToko;
const InfoToko({
super.key,
required this.namaToko,
required super.child,
});
// jalan pintas pengambilan data
static InfoToko of(BuildContext context) {
final info = context.dependOnInheritedWidgetOfExactType<InfoToko>();
assert(info != null, 'InfoToko tidak ditemukan di atas widget ini');
return info!;
}
@override
bool updateShouldNotify(InfoToko old) => namaToko != old.namaToko;
}
// dipakai di kedalaman berapa pun, tanpa oper parameter:
Text(InfoToko.of(context).namaToko)dependOnInheritedWidgetOfExactType melakukan dua hal sekaligus: mengambil data DAN mendaftarkan widget pemanggil sebagai pendengar. Saat namaToko berubah dan updateShouldNotify mengembalikan true, semua pendengar otomatis rebuild. Inilah cara kerja Theme.of(context) dan MediaQuery.of(context) di balik layar, jadi memahaminya berarti memahami dua API yang kamu pakai setiap hari.
Contoh Lengkap: Pengaturan Aplikasi
Pola yang lebih realistis: InheritedWidget yang datanya bisa berubah, dibungkus StatefulWidget yang memicu rebuild lewat setState:
class Pengaturan extends InheritedWidget {
final bool modeGelap;
final String bahasa;
const Pengaturan({
super.key,
required this.modeGelap,
required this.bahasa,
required super.child,
});
static Pengaturan of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<Pengaturan>()!;
// hanya rebuild pendengar jika ada yang benar-benar berubah
@override
bool updateShouldNotify(Pengaturan old) =>
modeGelap != old.modeGelap || bahasa != old.bahasa;
}
class PengaturanScope extends StatefulWidget {
final Widget child;
const PengaturanScope({super.key, required this.child});
@override
State<PengaturanScope> createState() => _PengaturanScopeState();
}
class _PengaturanScopeState extends State<PengaturanScope> {
bool _modeGelap = false;
void toggleGelap() => setState(() => _modeGelap = !_modeGelap);
@override
Widget build(BuildContext context) {
// setState di sini membangun ulang Pengaturan dengan nilai baru,
// dan semua widget yang memanggil Pengaturan.of ikut rebuild
return Pengaturan(
modeGelap: _modeGelap,
bahasa: 'id',
child: widget.child,
);
}
}Perhatikan: updateShouldNotify yang membandingkan tiap field mencegah rebuild sia-sia. Kalau ia selalu mengembalikan true, setiap setState sekecil apa pun membangun ulang semua pendengar, dan keuntungan mekanisme ini hilang.
Edge Case yang Wajib Tahu
context harus berada DI BAWAH InheritedWidget. Bug klasik: memanggil InfoToko.of(context) di dalam build yang sama dengan yang membuat InfoToko. Context itu milik widget yang sedang dibangun, dan ia belum "melihat" InheritedWidget yang baru dibuat di bawahnya. Solusinya: bungkus pemanggil dengan Builder, atau pindahkan pengambilan ke widget anak.
// SALAH: context belum berada di bawah InfoToko
@override
Widget build(BuildContext context) {
return InfoToko(
namaToko: 'Warkop',
child: Text(InfoToko.of(context).namaToko), // error!
);
}
// BENAR: Builder memberi context baru yang sudah di bawahnya
@override
Widget build(BuildContext context) {
return InfoToko(
namaToko: 'Warkop',
child: Builder(
builder: (ctx) => Text(InfoToko.of(ctx).namaToko),
),
);
}Dua cara mengambil, dua perilaku. dependOnInheritedWidgetOfExactType mendaftarkan dependency (widget rebuild saat data berubah). getElementForInheritedWidgetOfExactType hanya mengambil tanpa mendaftar, cocok untuk sekali baca di initState atau event handler yang tidak butuh rebuild otomatis.
Kapan TIDAK Memakai Langsung
Menulis InheritedWidget mentah itu verbosa: butuh class widget, static of, dan updateShouldNotify manual. Untuk state global aplikasi, pakai Provider atau Riverpod yang dibangun di atas konsep ini dengan API jauh lebih enak (termasuk rebuild selektif per field). Pakai InheritedWidget langsung hanya saat kamu membangun library sendiri, butuh kontrol penuh atas notifikasi, atau sedang belajar cara kerja Theme.of dari dalam.
Catatan teknis:
updateShouldNotifydipanggil dengan widget lama setiap kali parent rebuild. Jadikan perbandingannya murah (perbandingan field sederhana), karena method ini berjalan untuk setiap rebuild parent, bukan hanya saat data berubah.
Tantangan
Theme.of tiruan
Buat InheritedWidget WarnaAksen yang membawa sebuah Color. Tampilkan warnanya sebagai background Container di widget cucu (2 level di bawah).