Belajar Flutter dari Awal: Manajemen State Lanjutan dengan Provider (Part 8)

Rifqi An Rifqi An
Maret 01, 2026


Halo, para pendekar keyboard dan ksatria kopi! Balik lagi nih sama gue, di seri Belajar Flutter dari Awal. Gimana kabar jari-jemari kalian? Semoga nggak kriting karena kebanyakan ngoding, ya! Kita udah sampai di Part 8 nih, gila! Nggak kerasa, kan? Dari nol sampai sekarang udah mau ngomongin state management tingkat dewa.

Oke, di part-part sebelumnya kita udah kenalan sama yang namanya Provider. Udah tahu lah ya, itu semacam bapak kos yang bisa nyediain data buat anak-anak kos (widget) di sekitarnya. Tapi gimana kalau bapak kosnya banyak? Atau data yang disediain itu kompleks banget? Nah, di sinilah kita butuh jurus baru!

Siap-siap pegangan erat, sediakan kopi (atau teh, kalau lagi diet kafein), dan mari kita selami dunia Manajemen State Lanjutan dengan Provider!

Daftar Isi

Pendahuluan: Kenapa Harus Lanjutan?

Oke, di part sebelumnya kita udah bahas dasar-dasar Provider, termasuk ChangeNotifierProvider. Kalau cuma satu Provider buat satu state sederhana, itu gampang banget, kan? Ibarat jualan gorengan sebiji, ya tinggal goreng aja. Tapi gimana kalau kita mau bikin aplikasi beneran? Aplikasi yang punya banyak fitur, banyak data, dan banyak interaksi?

Misalnya, aplikasi toko online. Ada daftar produk, ada keranjang belanja, ada data user, ada history transaksi, ada diskon, dll. Nah, kalau semua itu cuma pakai satu ChangeNotifierProvider, atau bikin ChangeNotifierProvider satu per satu di setiap widget, dijamin pusing tujuh keliling! Kodenya bakal jadi benang kusut cucian, susah di-maintain, dan gampang banget kena bug yang bikin aplikasi jadi error. Jangan sampai aplikasinya jadi kayak benang kusut cucian yang bikin pusing pas mau nyari kaos kaki kembarannya.

Makanya, kita perlu jurus-jurus lanjutan biar ngodingnya tetap santuy, rapi, dan performanya juga optimal. Nggak mau kan, aplikasi kita lemot cuma gara-gara salah manajemen state? Dijamin langsung kena review bintang satu di Play Store atau App Store, ngeri!

Review Singkat: Provider Itu Apa Sih?

Sebelum kita loncat ke yang advance, yuk kita kilas balik sebentar. Provider itu intinya adalah sebuah paket (package) di Flutter yang memudahkan kita untuk "menyediakan" (provide) data atau "mendengarkan" (consume) perubahan data di seluruh aplikasi kita.

Konsep dasarnya simpel banget:

  • Kita punya sebuah data (misal, hitungan angka, daftar produk, status login).
  • Data itu kita bungkus pakai ChangeNotifier (kalau datanya bisa berubah dan kita mau UI di-update otomatis).
  • Terus, data yang udah dibungkus itu kita "sediakan" pakai ChangeNotifierProvider di pohon widget kita.
  • Di mana pun di bawah Provider itu, kita bisa "mengambil" atau "mendengarkan" data tersebut.

Contoh sederhana ChangeNotifierProvider yang mungkin udah kalian pakai:


// counter_provider.dart
import 'package:flutter/foundation.dart';

class CounterProvider with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // Beritahu widget yang mendengarkan bahwa ada perubahan
  }
}

// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_provider.dart';

void main() {
  runApp(
    ChangeNotifierProvider( // Nah ini dia!
      create: (context) => CounterProvider(),
      child: MyApp(),
    ),
  );
}

// di suatu widget
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Mengambil nilai count dari CounterProvider
    final counter = Provider.of<CounterProvider>(context);

    return Text('Count: ${counter.count}');
  }
}

Gampang, kan? Tapi itu cuma buat satu. Gimana kalau seribu? Oke, seribu lebay sih, tapi kalau ada puluhan gimana?

Mengapa Kita Butuh State Management yang Lebih Advanced?

Ketika State Menggila: Banyak Banget!

Bayangin kita punya aplikasi yang udah agak besar:

  • AuthProvider: Untuk status login user.
  • ProductProvider: Daftar semua produk.
  • CartProvider: Item-item di keranjang belanja.
  • OrderProvider: History pesanan user.
  • ThemeProvider: Untuk tema terang/gelap aplikasi.
  • ...dan masih banyak lagi!

Kalau kita cuma pakai ChangeNotifierProvider satu per satu, nanti kodenya bisa jadi begini di main.dart:


void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => AuthProvider(),
      child: ChangeNotifierProvider(
        create: (context) => ProductProvider(),
        child: ChangeNotifierProvider(
          create: (context) => CartProvider(),
          child: ChangeNotifierProvider(
            create: (context) => OrderProvider(),
            child: MyApp(),
          ),
        ),
      ),
    ),
  );
}

Waduh, itu namanya "Provider Hell"! Kodenya jadi kayak piramida terbalik, susah dibaca, dan kalau mau nambah satu lagi harus nambah nesting lagi. Selain itu, kalau cuma pakai Provider.of<T>(context) biasa, semua widget yang "mendengarkan" provider itu bakal di-rebuild (dibangun ulang) setiap kali ada perubahan, padahal mungkin cuma sebagian kecil dari state yang mereka butuhkan.

Nah, biar nggak pusing dan biar aplikasi kita tetap ngebut, mari kita kenalan sama teman-teman baru!

Jurus Jitu Provider untuk State yang Kompleks

MultiProvider: Solusi untuk Banyak Provider (Kayak Kondangan!)

MultiProvider itu kayak tenda kondangan besar yang isinya banyak meja prasmanan (provider). Jadi, daripada bikin tenda kecil satu-satu yang bikin sempit, mending bikin satu tenda besar aja dan semua provider masuk situ.

Dengan MultiProvider, kita bisa mendaftarkan banyak ChangeNotifierProvider (atau jenis provider lainnya) di satu tempat secara rapi.


import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// Anggap kita punya provider-provider ini
import 'auth_provider.dart';
import 'product_provider.dart';
import 'cart_provider.dart';
import 'theme_provider.dart';

void main() {
  runApp(
    MultiProvider( // Ini dia jagoannya!
      providers: [
        ChangeNotifierProvider(create: (context) => AuthProvider()),
        ChangeNotifierProvider(create: (context) => ProductProvider()),
        ChangeNotifierProvider(create: (context) => CartProvider()),
        ChangeNotifierProvider(create: (context) => ThemeProvider()),
      ],
      child: MyApp(),
    ),
  );
}

Lihat kan? Lebih rapi, lebih mudah dibaca, dan nggak ada lagi piramida terbalik yang bikin kepala nyut-nyutan. Semua provider dikumpulkan dalam satu daftar providers: [...]. Mantap jiwa!

Selector: Memilih Bagian State yang Relevan (Biar Nggak Boros!)

Selector ini adalah hero-nya performa! Bayangin kita punya data user yang besar banget (nama, alamat, email, foto profil, riwayat transaksi, dll.). Kalau kita cuma mau nampilin nama user di header aplikasi, apakah kita perlu me-rebuild seluruh widget header kalau ada data user lain yang berubah (misal, user update alamat)? Tentu tidak! Itu namanya boros sumber daya.

Selector memungkinkan kita untuk "memilih" (select) hanya bagian kecil dari state yang kita butuhkan. Widget yang menggunakan Selector hanya akan di-rebuild jika bagian state yang dipilih itu berubah.


// Contoh AuthProvider (Anggap punya property namaUser)
class AuthProvider with ChangeNotifier {
  String _userName = 'John Doe';
  String _userEmail = 'john.doe@example.com';
  // ... data user lainnya

  String get userName => _userName;
  String get userEmail => _userEmail;

  void updateUserName(String newName) {
    _userName = newName;
    notifyListeners(); // Hanya ini yang berubah, bukan email
  }
  // ... metode lain
}

// Cara menggunakan Selector
class UserNameDisplay extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Selector<AuthProvider, String>( // <T, R> T=tipe provider, R=tipe data yang dipilih
      selector: (context, authProvider) => authProvider.userName, // Pilih hanya userName
      builder: (context, userName, child) {
        print('UserNameDisplay rebuild: $userName'); // Ini cuma jalan kalau userName berubah
        return Text('Halo, <strong>$userName</strong>!');
      },
    );
  }
}

// Sementara jika kita pakai Consumer biasa:
class UserEmailDisplay extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Kalau pakai Consumer, widget ini bisa di-rebuild kalau ada perubahan di AuthProvider,
    // meskipun yang berubah cuma userName, bukan userEmail.
    return Consumer<AuthProvider>(
      builder: (context, authProvider, child) {
        print('UserEmailDisplay rebuild: ${authProvider.userEmail}'); // Ini jalan kalau userName berubah juga
        return Text('Emailmu: ${authProvider.userEmail}');
      },
    );
  }
}

Keren kan Selector ini? Dia punya dua argumen penting:

  • selector: Sebuah fungsi yang mengambil provider dan mengembalikan data spesifik yang ingin kita "dengarkan".
  • builder: Fungsi yang membangun UI kita, tapi hanya akan dipanggil ulang kalau nilai yang dikembalikan oleh selector berubah (berdasarkan perbandingan operator ==).

Intinya, pakai Selector itu kayak lagi milih-milih barang di supermarket. Kamu cuma ambil yang kamu butuhin, nggak semua barang di rak kamu angkut. Efisien!

Consumer: Si Tua Keladi yang Tetap Berguna

Meskipun ada Selector yang canggih, Consumer yang sudah kita kenal tetap punya tempatnya. Consumer itu cara paling gampang untuk mengakses provider dan me-rebuild widget saat ada perubahan.


class MySimpleWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<CounterProvider>(
      builder: (context, counter, child) {
        return Text('Jumlah saat ini: ${counter.count}');
      },
    );
  }
}

Kapan pakai Consumer?

  • Kalau widget yang kamu buat memang perlu di-rebuild setiap kali ada perubahan apapun di dalam provider.
  • Kalau data yang kamu akses dari provider itu tunggal atau sederhana dan nggak ada risiko performa besar.
  • Untuk kasus yang cepat dan tidak terlalu kompleks.

Jadi, jangan benci Consumer. Dia itu seperti teman lama yang selalu ada. Kadang kita butuh yang baru dan lebih canggih (Selector), tapi yang lama pun tetap setia dan bisa diandalkan.

Studi Kasus: Aplikasi Toko Kopi Online (dengan Keranjang Belanja)

Oke, biar nggak cuma teori, yuk kita coba bikin studi kasus sederhana: aplikasi toko kopi online! Aplikasi ini punya daftar kopi dan fitur keranjang belanja. Ini adalah skenario klasik di mana MultiProvider dan Selector akan sangat membantu.

Bikin Model Data Kopi & Item Keranjang

Kita butuh dua model sederhana:

  • Kopi: Untuk informasi nama dan harga kopi.
  • ItemKeranjang: Untuk menyimpan kopi apa saja yang ada di keranjang, beserta jumlahnya.

// models/kopi.dart
class Kopi {
  final String nama;
  final double harga;

  Kopi({required this.nama, required this.harga});

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Kopi && runtimeType == other.runtimeType && nama == other.nama;

  @override
  int get hashCode => nama.hashCode;
}

// models/item_keranjang.dart
import 'package:provider_advanced/models/kopi.dart';

class ItemKeranjang {
  final Kopi kopi;
  int jumlah;

  ItemKeranjang({required this.kopi, this.jumlah = 1});

  double get totalHarga => kopi.harga * jumlah;
}

Kenapa ada operator == dan hashCode di kelas Kopi? Ini penting banget kalau nanti kita mau bandingin objek Kopi, misalnya saat mencari kopi di keranjang. Kalau nggak di-override, dua objek Kopi dengan nama yang sama akan dianggap berbeda oleh Dart!

ChangeNotifier untuk State Toko dan Keranjang

Kita akan punya dua ChangeNotifier:

  • TokoKopiProvider: Menyimpan daftar kopi yang dijual.
  • KeranjangProvider: Menyimpan daftar ItemKeranjang dan menghitung total harga.

// providers/toko_kopi_provider.dart
import 'package:flutter/foundation.dart';
import 'package:provider_advanced/models/kopi.dart';

class TokoKopiProvider with ChangeNotifier {
  final List<Kopi> _daftarKopi = [
    Kopi(nama: 'Espresso', harga: 25000),
    Kopi(nama: 'Latte', harga: 30000),
    Kopi(nama: 'Cappuccino', harga: 32000),
    Kopi(nama: 'Americano', harga: 28000),
    Kopi(nama: 'Mochaccino', harga: 35000),
  ];

  List<Kopi> get daftarKopi => _daftarKopi;
}

// providers/keranjang_provider.dart
import 'package:flutter/foundation.dart';
import 'package:provider_advanced/models/item_keranjang.dart';
import 'package:provider_advanced/models/kopi.dart';

class KeranjangProvider with ChangeNotifier {
  final List<ItemKeranjang> _items = [];

  List<ItemKeranjang> get items => _items;

  double get totalHarga {
    return _items.fold(0.0, (sum, item) => sum + item.totalHarga);
  }

  int get jumlahItem => _items.fold(0, (sum, item) => sum + item.jumlah);

  void tambahKeKeranjang(Kopi kopi) {
    int index = _items.indexWhere((item) => item.kopi == kopi);
    if (index != -1) {
      _items[index].jumlah++;
    } else {
      _items.add(ItemKeranjang(kopi: kopi));
    }
    notifyListeners();
  }

  void hapusDariKeranjang(Kopi kopi) {
    int index = _items.indexWhere((item) => item.kopi == kopi);
    if (index != -1) {
      if (_items[index].jumlah > 1) {
        _items[index].jumlah--;
      } else {
        _items.removeAt(index);
      }
      notifyListeners();
    }
  }

  void clearKeranjang() {
    _items.clear();
    notifyListeners();
  }
}

Implementasi MultiProvider

Sekarang, kita gabungkan kedua provider ini di main.dart menggunakan MultiProvider.


// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_advanced/providers/keranjang_provider.dart';
import 'package:provider_advanced/providers/toko_kopi_provider.dart';
import 'package:provider_advanced/screens/home_screen.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider( // Ini dia MultiProvider-nya
      providers: [
        ChangeNotifierProvider(create: (context) => TokoKopiProvider()),
        ChangeNotifierProvider(create: (context) => KeranjangProvider()),
      ],
      child: MaterialApp(
        title: 'Toko Kopi Bang Jago',
        theme: ThemeData(
          primarySwatch: Colors.brown,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: HomeScreen(),
      ),
    );
  }
}

Lihat, kan? main.dart kita tetap rapi meskipun ada dua provider. Kalau besok nambah AuthProvider atau ThemeProvider, tinggal tambahin aja di list providers!

Ngoding UI dengan Selector dan Consumer

Sekarang, kita akan bangun UI untuk menampilkan daftar kopi dan keranjang belanja.


// screens/home_screen.dart
import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; // Untuk format mata uang
import 'package:provider/provider.dart';
import 'package:provider_advanced/models/item_keranjang.dart';
import 'package:provider_advanced/models/kopi.dart';
import 'package:provider_advanced/providers/keranjang_provider.dart';
import 'package:provider_advanced/providers/toko_kopi_provider.dart';

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final keranjangProvider = Provider.of<KeranjangProvider>(context, listen: false);

    return Scaffold(
      appBar: AppBar(
        title: Text('Toko Kopi Bang Jago'),
        actions: [
          IconButton(
            icon: Icon(Icons.shopping_cart),
            onPressed: () {
              // Show keranjang belanja
              showModalBottomSheet(
                context: context,
                builder: (context) => KeranjangSheet(),
              );
            },
          ),
          // Menggunakan Selector untuk menampilkan jumlah item di keranjang
          Padding(
            padding: const EdgeInsets.only(right: 16.0),
            child: Center(
              child: Selector<KeranjangProvider, int>(
                selector: (context, keranjang) => keranjang.jumlahItem,
                builder: (context, jumlahItem, child) {
                  return Text(
                    jumlahItem > 0 ? '$jumlahItem' : '',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  );
                },
              ),
            ),
          ),
        ],
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Text(
              'Pilih Kopi Favoritmu!',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
          ),
          Expanded(
            child: Consumer<TokoKopiProvider>( // Menggunakan Consumer untuk daftar kopi
              builder: (context, tokoKopi, child) {
                return ListView.builder(
                  itemCount: tokoKopi.daftarKopi.length,
                  itemBuilder: (context, index) {
                    final kopi = tokoKopi.daftarKopi[index];
                    return Card(
                      margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                      child: ListTile(
                        leading: Icon(Icons.coffee),
                        title: Text(kopi.nama),
                        subtitle: Text(NumberFormat.currency(
                                locale: 'id_ID', symbol: 'Rp ', decimalDigits: 0)
                            .format(kopi.harga)),
                        trailing: IconButton(
                          icon: Icon(Icons.add_shopping_cart),
                          onPressed: () {
                            keranjangProvider.tambahKeKeranjang(kopi);
                            ScaffoldMessenger.of(context).showSnackBar(
                              SnackBar(content: Text('${kopi.nama} ditambahkan!')),
                            );
                          },
                        ),
                      ),
                    );
                  },
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

class KeranjangSheet extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(16.0),
      child: Consumer<KeranjangProvider>( // Consumer untuk seluruh keranjang
        builder: (context, keranjang, child) {
          if (keranjang.items.isEmpty) {
            return Center(
              child: Text('Keranjang kosong, yuk belanja!', style: TextStyle(fontSize: 18)),
            );
          }
          return Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text('Isi Keranjangmu:', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
              SizedBox(height: 10),
              Expanded(
                child: ListView.builder(
                  itemCount: keranjang.items.length,
                  itemBuilder: (context, index) {
                    final item = keranjang.items[index];
                    return ListTile(
                      title: Text('${item.kopi.nama} x${item.jumlah}'),
                      subtitle: Text(NumberFormat.currency(
                              locale: 'id_ID', symbol: 'Rp ', decimalDigits: 0)
                          .format(item.totalHarga)),
                      trailing: Row(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          IconButton(
                            icon: Icon(Icons.remove_circle),
                            onPressed: () => keranjang.hapusDariKeranjang(item.kopi),
                          ),
                          IconButton(
                            icon: Icon(Icons.add_circle),
                            onPressed: () => keranjang.tambahKeKeranjang(item.kopi),
                          ),
                        ],
                      ),
                    );
                  },
                ),
              ),
              Divider(),
              // Menggunakan Selector untuk total harga, biar hanya di-rebuild kalau harga berubah
              Selector<KeranjangProvider, double>(
                selector: (context, keranjang) => keranjang.totalHarga,
                builder: (context, totalHarga, child) {
                  return Text(
                    'Total: ${NumberFormat.currency(
                            locale: 'id_ID', symbol: 'Rp ', decimalDigits: 0)
                        .format(totalHarga)}',
                    style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  );
                },
              ),
              SizedBox(height: 20),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: [
                  ElevatedButton(
                    onPressed: () {
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text('Selamat! Pesananmu sedang diproses!')),
                      );
                      keranjang.clearKeranjang();
                      Navigator.pop(context);
                    },
                    child: Text('Checkout'),
                  ),
                  ElevatedButton(
                    onPressed: () {
                      keranjang.clearKeranjang();
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text('Keranjang dikosongkan!')),
                      );
                    },
                    child: Text('Kosongkan Keranjang'),
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.redAccent,
                    ),
                  ),
                ],
              )
            ],
          );
        },
      ),
    );
  }
}

Perhatikan beberapa poin penting di kode di atas:

  • Di AppBar, untuk menampilkan jumlah item di keranjang, kita pakai Selector<KeranjangProvider, int>. Ini penting karena kita hanya butuh nilai jumlahItem dari KeranjangProvider. Kalau ada perubahan lain di KeranjangProvider (misal, update jumlah item yang sudah ada), tapi jumlahItem-nya nggak berubah, widget jumlah di AppBar ini nggak akan di-rebuild. Hemat daya!
  • Untuk daftar kopi, kita pakai Consumer<TokoKopiProvider>. Karena daftar kopi ini statis (nggak berubah-ubah setelah di-load), sebenarnya bisa juga pakai Provider.of<TokoKopiProvider>(context, listen: false) di atas build method, tapi pakai Consumer di sini juga nggak masalah karena dia cuma satu kali ambil data.
  • Di KeranjangSheet, untuk menampilkan seluruh isi keranjang, kita pakai Consumer<KeranjangProvider>. Ini karena kita butuh semua data item keranjang.
  • Tapi, untuk menampilkan total harga di KeranjangSheet, kita kembali pakai Selector<KeranjangProvider, double>. Kenapa? Karena total harga itu cuma angka, dan widget Text yang menampilkannya cuma perlu di-rebuild kalau angka total harganya berubah. Jadi, kalau misalnya user nambah kopi yang udah ada di keranjang (jumlahnya nambah, tapi list item-nya tidak berubah strukturnya), total harga akan berubah, dan Selector ini akan me-rebuild hanya bagian Text total harga, bukan seluruh KeranjangSheet.

Kombinasi MultiProvider, Selector, dan Consumer ini bener-bener jurus ampuh untuk bikin aplikasi Flutter yang kompleks tapi tetap performant dan mudah di-maintain. Kita bisa memilih kapan harus "mendengarkan" seluruh perubahan (Consumer) atau hanya bagian spesifik (Selector), dan MultiProvider bikin semua penataan provider jadi rapi!

Kesimpulan: Kapan Pakai yang Mana dan Manfaatnya

Oke, jadi kita udah belajar banyak hari ini, dari yang tadinya cuma ChangeNotifierProvider sendirian, sekarang udah kenalan sama teman-temannya yang lebih canggih.

  • MultiProvider: Wajib banget dipakai kalau aplikasi kalian punya lebih dari satu provider. Ini solusi paling rapi dan bersih untuk mendaftarkan banyak provider di satu tempat (biasanya di paling atas pohon widget, seperti di main.dart). Hindari "Provider Hell" dengan menumpuk ChangeNotifierProvider berlapis-lapis!
  • Selector: Ini pilihan utama kalau kamu mau mengoptimalkan performa. Gunakan Selector saat kamu hanya butuh sebagian kecil dari state dalam sebuah provider, dan kamu ingin widgetmu hanya di-rebuild ketika bagian spesifik itu berubah. Ini penting banget untuk widget yang sering di-rebuild atau di aplikasi yang performanya kritis.
  • Consumer: Masih sangat berguna dan mudah digunakan. Pakai Consumer kalau widgetmu memang perlu di-rebuild setiap kali ada perubahan apapun di dalam provider, atau kalau state yang kamu akses itu tunggal dan sederhana. Untuk kasus yang cepat dan tidak terlalu kompleks, Consumer sudah lebih dari cukup.

Dengan menguasai ketiga jurus ini, kalian sudah siap menghadapi tantangan manajemen state di aplikasi Flutter yang lebih besar dan kompleks. Nggak ada lagi drama ngoding yang bikin kepala mau pecah karena state berantakan. Ingat, state management yang baik itu bukan cuma soal rapi, tapi juga soal performa dan kemudahan dalam pengembangan di masa depan.

Selamat ngoding, jangan lupa istirahat, dan ngopi biar tetap waras! Sampai jumpa di part selanjutnya, ya!

Latihan: Simulasi Antrean Wartel Era 90-an

Oke, biar ilmu yang tadi nggak cuma numpang lewat di kepala, yuk kita bikin sebuah simulasi sederhana yang kocak tapi butuh state management lanjutan. Tugas kalian adalah membuat simulasi "Antrean Wartel Era 90-an"!

Skenario: Di sebuah wartel, ada beberapa bilik telepon (misal 3 bilik). Orang-orang datang dan masuk antrean. Begitu ada bilik yang kosong, orang di antrean paling depan bisa masuk. Kalian harus menampilkan status bilik, daftar antrean, dan mungkin total orang di antrean.

Fitur yang Harus Ada:

  1. Daftar Bilik Telepon: Tampilkan 3 bilik. Setiap bilik bisa berstatus "Kosong" atau "Terisi oleh [Nama Pengantre]".
  2. Tombol "Antre Dong, Mba/Mas!": Ketika tombol ini ditekan, seorang "pengantre baru" (dengan nama acak atau nama yang diinput) akan ditambahkan ke daftar antrean.
  3. Daftar Antrean: Tampilkan siapa saja yang sedang mengantre, beserta nomor urutnya.
  4. Tombol "Panggilan Selesai (Bilik X)": Setiap bilik yang terisi harus punya tombol untuk menandakan panggilan selesai. Ketika tombol ini ditekan, bilik akan kosong, dan pengantre berikutnya dari daftar antrean akan masuk ke bilik tersebut (jika ada).
  5. Info Total Antrean: Tampilkan berapa total orang yang sedang mengantre.

Requirement Spesial:

  • Gunakan MultiProvider untuk mengelola state. Kalian mungkin butuh setidaknya dua provider:
    • BilikProvider: Mengelola status masing-masing bilik (siapa yang pakai, durasi panggilan, dll).
    • AntreanProvider: Mengelola daftar orang yang sedang mengantre.
  • Gunakan Selector di beberapa bagian UI untuk memastikan hanya bagian yang relevan yang di-rebuild. Contoh:
    • Menampilkan status bilik (Selector untuk status bilik tertentu).
    • Menampilkan total orang di antrean (Selector untuk jumlah antrean).
  • Gunakan Consumer di bagian UI yang memang perlu mendengarkan semua perubahan dari provider (misal, daftar lengkap antrean).
  • Jangan lupa humor receh! Mungkin nama pengantre acak bisa kayak "Bapak RT", "Ibu Arisan", "Mas Jomblo", atau "Pak Satpam".

Ini akan jadi latihan yang seru dan menantang untuk mengaplikasikan apa yang sudah kita pelajari. Selamat mencoba, para programmer tangguh! Jangan sampai aplikasimu ngadat kayak pulsa wartel habis di tengah obrolan penting!

Bagikan Artikel Ini