inheritedstatethemeMenengah5 mnt baca

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.

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

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

dart
// 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: updateShouldNotify dipanggil 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).