Topik ini memperkenalkan package Provider, yaitu package pengelolaan state yang direkomendasikan sebagai titik awal untuk state tingkat aplikasi di Flutter. Pada topik sebelumnya, InheritedWidget sudah dikenal sebagai mekanisme dasar berbagi data lintas subpohon, tetapi penggunaannya masih menuntut kode yang rinci dan state yang berubah belum memiliki tempat penyimpanan yang tepat. Di sini state dipindahkan ke kelas tersendiri yang diturunkan dari ChangeNotifier, didaftarkan pada pohon widget melalui ChangeNotifierProvider, lalu dibaca dari widget mana pun melalui context.watch dan context.read. Setelah topik ini dipraktikkan, state lintas layar tidak lagi dilewatkan melalui rangkaian konstruktor.
Dari InheritedWidget ke package Provider#
Package Provider dibangun di atas mekanisme InheritedWidget, sehingga cara kerja pendaftaran dependen yang sudah dipelajari tetap berlaku. Package ini menambahkan dua hal yang sebelumnya harus ditulis manual. Pertama, widget penyedia siap pakai untuk setiap jenis data, sehingga subclass InheritedWidget dan method of tidak perlu ditulis lagi. Kedua, pengelolaan siklus hidup objek state, yaitu objek state dibuat ketika pertama kali dibutuhkan dan dibuang secara otomatis ketika penyedianya lepas dari pohon widget.
Pemasangan dilakukan dengan menambahkan package ke pubspec.yaml, lalu menjalankan flutter pub get. Perintah flutter pub add provider melakukan keduanya sekaligus. Baris yang diperlukan pada pubspec.yaml berupa nama package, rentang versi Dart, dan dependensi provider berikut.
name: daftar_belanja
description: Aplikasi daftar belanja pribadi dengan package Provider.
environment:
sdk: ^3.13.0
dependencies:
flutter:
sdk: flutter
provider: ^6.1.5
flutter:
uses-material-design: true# tidak-dijalankan
flutter pub add providerKelas state dengan ChangeNotifier#
ChangeNotifier adalah kelas bawaan Flutter yang menyediakan mekanisme pemberitahuan bagi pendengar (listener), yaitu objek yang mendaftar agar diberi tahu setiap kali state berubah. Kelas state aplikasi diturunkan dari ChangeNotifier, lalu setiap method yang mengubah state diakhiri pemanggilan notifyListeners(). Pemanggilan tersebut memicu rebuild seluruh widget yang berlangganan.
Contoh berikut memindahkan state daftar belanja ke kelas DaftarBelanja. Daftar disimpan pada field privat, sedangkan getter mengembalikan salinan yang tidak dapat diubah agar bagian luar tidak dapat menyunting isinya tanpa melewati method kelas ini.
// tidak-dijalankan
class DaftarBelanja extends ChangeNotifier {
final _barang = <String>[];
List<String> get barang => List.unmodifiable(_barang);
void tambah(String nama) {
if (nama.isEmpty) {
return;
}
_barang.add(nama);
notifyListeners();
}
void hapus(String nama) {
_barang.remove(nama);
notifyListeners();
}
}Pada kelas di atas, kedua method yang mengubah isi daftar diakhiri notifyListeners(), sedangkan pembacaan melalui getter tidak memicu apa pun. Pola ini menjaga satu aturan sederhana, yaitu perubahan state hanya boleh terjadi melalui method kelas tersebut.
Mendaftarkan state pada pohon widget#
ChangeNotifierProvider menggabungkan kedua bagian tersebut dalam satu widget, yaitu menyimpan objek ChangeNotifier di dalam mekanisme InheritedWidget dan mengelola siklus hidupnya. Parameter create berisi fungsi yang membuat objek state, dan fungsi itu baru dipanggil ketika state pertama kali dibutuhkan. Ketika penyedia lepas dari pohon widget, objek state yang dibuatnya ikut dibuang secara otomatis melalui dispose, sehingga tidak ada pembersihan manual.
Penyedia dipasang di atas seluruh widget yang menggunakannya. State yang digunakan lintas layar biasanya diletakkan di atas MaterialApp.
// tidak-dijalankan
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => DaftarBelanja(),
child: const AplikasiBelanja(),
),
);
}Setelah penyedia terpasang, seluruh widget di bawahnya dapat mengakses DaftarBelanja dari BuildContext, tanpa penerusan konstruktor dan tanpa prop drilling. Cakupannya mengikuti posisi penyedia pada pohon, sehingga state yang digunakan satu layar saja cukup diletakkan lebih rendah dari MaterialApp.
context.watch dan context.read#
Ekstensi context dari package Provider menyediakan dua cara mengakses state untuk keperluan yang berbeda. context.watch<T>() mengambil nilai T sekaligus mendaftarkan widget pemanggil sebagai dependen dari penyedianya, sehingga setiap notifyListeners() memicu build ulang widget tersebut. context.read<T>() hanya mengambil nilai saat itu tanpa mendaftarkan apa pun, sehingga widget pemanggil tidak ikut dibangun ulang ketika state berubah.
Aturan penggunaannya mengikuti tempat pemanggilan. watch dipanggil di dalam build, karena di sanalah widget menyusun tampilan dari state dan wajib berlangganan pembaruannya. read dipanggil di dalam callback seperti onPressed, karena aksesnya hanya terjadi sekali dan tidak ada tampilan yang bergantung padanya.
// tidak-dijalankan
class LayarBelanja extends StatelessWidget {
const LayarBelanja({super.key});
@override
Widget build(BuildContext context) {
final barang = context.watch<DaftarBelanja>().barang;
return Scaffold(
appBar: AppBar(title: Text('Belanja (${barang.length})')),
body: ListView(
children: [
for (final nama in barang) ListTile(title: Text(nama)),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read<DaftarBelanja>().tambah('Gula');
},
child: const Icon(Icons.add),
),
);
}
}Pada widget LayarBelanja di atas, watch membuat AppBar dan daftar selalu menampilkan isi terbaru, sedangkan read di dalam tombol hanya perlu memanggil tambah() tanpa berlangganan. Perubahan state tetap sampai ke tampilan karena tambah() memanggil notifyListeners(), lalu widget yang berlangganan membangun ulang dirinya sendiri.
MultiProvider#
Aplikasi nyata biasanya menyimpan lebih dari satu kelas state, misalnya daftar belanja dan mode tampilan. Tanpa alat bantu, penyedia-penyedia tersebut disusun bertingkat, satu di dalam yang lain, sehingga susunannya cepat sulit dibaca. MultiProvider menyusun beberapa penyedia secara berurutan dan menghasilkan susunan pohon yang sama.
// tidak-dijalankan
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => DaftarBelanja()),
ChangeNotifierProvider(create: (_) => ModeTampilan()),
],
child: const AplikasiBelanja(),
),
);
}Setiap entri pada providers bekerja persis seperti penyedia tunggal, sehingga keputusan berbagi state tetap terkendali per kelas. Urutan penulisan menentukan susunan pohon, karena entri yang lebih dahulu menjadi induk bagi entri di bawahnya.
Kesalahan umum#
- Menampilkan state dengan
context.read. Nilainya hanya benar pada saat itu dan tidak diperbarui ketika state berubah, sehingga tampilan tampak tertinggal. Gunakancontext.watchdi dalambuilduntuk setiap nilai yang ditampilkan. - Lupa memanggil
notifyListeners()setelah mengubah state. Tidak ada pemberitahuan yang dikirim, sehingga widget yang berlangganan tetap menampilkan nilai lama. Pastikan setiap method yang mengubah state diakhiri pemanggilan tersebut. - Memaparkan koleksi state secara langsung. Getter yang mengembalikan list internal memungkinkan bagian lain aplikasi memanggil
add()tanpa pemberitahuan, sehingga state dan tampilan tidak sinkron. KembalikanList.unmodifiabledan sediakan method khusus untuk perubahan.
Ringkasan#
- Kelas state diturunkan dari
ChangeNotifier, dan setiap perubahan state diakhirinotifyListeners(). ChangeNotifierProvidermendaftarkan objek state pada pohon widget, membuatnya saat pertama kali dibutuhkan, dan membuangnya secara otomatis.context.watchberlangganan pembaruan dan digunakan di dalambuild, sedangkancontext.readmengambil nilai saat dipanggil tanpa berlangganan.MultiProvidermenyusun beberapa kelas state sekaligus tanpa penyusunan bertingkat yang sulit dibaca.
Referensi#
- provider — pub.dev
— Halaman resmi package Provider beserta dokumentasi
ChangeNotifierProviderdan ekstensicontext. - Simple app state management — Flutter — Panduan resmi pendekatan sederhana pengelolaan state dengan package Provider.
- ChangeNotifier class — Flutter API
— Referensi kelas
ChangeNotifierdan kontraknotifyListeners().