BAB 9 / 9
Slide 1 / 23
Master Class Flutter
STATE
FLUTTER
setState
WIDGET
BUILD
DART

BAB 9

STATE MANAGEMENT DASAR

Flutter Mobile Development • Kelas 10 • Semester 1

Apa yang Akan Kamu Pelajari?

8 Topik utama dalam perjalanan State Management

Perjalananmu:   BAB 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → BAB 9 ✦

① Stateless Widget

Widget tanpa perubahan — Slide 4

② Stateful Widget

Widget yang bisa berubah — Slide 5

③ setState()

Memicu update UI — Slide 7

④ Lifecycle

initState, build, dispose — Slide 8-11

⑤ State Mgmt Concept

Sumber data tunggal — Slide 12

⑥ Data Flow

Aliran data top-down — Slide 13

⑦ Rebuild Widget

Kapan UI digambar ulang — Slide 14

⑧ Mini Project

Counter App Advanced — Slide 15-19

State? Apa Itu State?

🚦 Analogi: Lampu Lalu Lintas

Warna lampu yang sedang menyala adalah STATE. Ketika waktu habis (event), state berubah → tampilan ikut berubah. Perhatikan lampu di samping berganti otomatis!

EVENT (waktu habis)
STATE BERUBAH
UI UPDATE

Definisi: State adalah DATA yang bisa BERUBAH, yang mempengaruhi tampilan UI sebuah widget di Flutter.

"UI adalah cerminan dari State saat ini."

Stateless Widget — Widget Tanpa Perubahan

🖼️ Analogi: Foto di Dinding

Foto dalam bingkai di dinding tidak bisa berubah sendiri. Kamu menekannya — tidak terjadi apa-apa. Stateless widget sama: tampilannya tetap, hanya bergantung pada data yang diberikan dari luar.

Karakteristik

  • Tidak memiliki state internal
  • Hanya menerima data dari luar (parameter / constructor)
  • Cocok untuk widget yang tidak perlu berubah
  • Lebih ringan dan efisien
kartu_nama.dart
class KartuNama extends StatelessWidget {
  final String nama;
  final String kelas;

  const KartuNama({
    Key? key,
    required this.nama,
    required this.kelas,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        children: [
          Text(nama, style: TextStyle(fontSize: 24)),
          Text(kelas, style: TextStyle(color: Colors.grey)),
        ],
      ),
    );
  }
}
Sahil Pratama
Kelas X RPL 1

↑ Tampilan tetap, tidak berubah

Stateful Widget — Widget yang Bisa Berubah

📺 Analogi: Remote TV

Tekan tombol volume + maka angka volume naik; ganti channel maka tampilan berubah. Menekan remote = mengubah STATE → tampilan ikut berubah. Coba demo di bawah!

📺 Volume & channel berubah → STATE berubah → tampilan ikut hidup

Karakteristik

  • Memiliki state internal yang bisa berubah
  • Menggunakan setState() untuk update UI
  • Memiliki lifecycle (initState, build, dispose)
  • Cocok untuk form, animasi, data dinamis
kartu_digital.dart
class KartuDigital extends StatefulWidget {
  const KartuDigital({Key? key}) : super(key: key);

  @override
  State<KartuDigital> createState() => _KartuDigitalState();
}

class _KartuDigitalState extends State<KartuDigital> {
  int jumlahKlik = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Diklik: $jumlahKlik kali'),
        ElevatedButton(
          onPressed: () => setState(() => jumlahKlik++),
          child: Text('Klik Saya!'),
        ),
      ],
    );
  }
}
Diklik: 0 kali

↑ Simulasi setState — angka berubah

Stateless vs Stateful — Pilih yang Mana?

AspekStatelessStateful
State Internal✗ Tidak Ada✓ Ada
Perubahan UI✗ Tidak Bisa✓ Bisa
LifecycleSederhanaLengkap
PerformaLebih RinganLebih Berat
ContohTeks, Ikon, LogoCounter, Form

Flowchart: Kapan Pakai Mana?

Apakah widget perlu berubah?
Tidak → Stateless Widget ✓
Ya, tapi data dari parent cukup → Stateless + Parameter ✓
Ya, butuh data internal → Stateful Widget ✓

setState() — Memberitahu Flutter untuk Update

✏️ Analogi: Papan Tulis di Kelas
5

Guru menghapus angka lama lalu menulis angka baru. setState() = menghapus tampilan lama dan menggambar ulang. Klik tombol untuk mencoba!

Alur setState

Pengguna klik tombol
setState(() { nilai++ })
Flutter: "Ada perubahan state!"
Panggil build() lagi
UI di-update
setstate.dart
// ❌ SALAH — tidak akan update UI
void tambahNilai() {
  nilai++; // Tidak pakai setState!
}

// ✓ BENAR — UI akan update
void tambahNilai() {
  setState(() {
    nilai++; // Di dalam setState
  });
}
⚠️ Peringatan: Jangan lakukan operasi berat (network/DB) di dalam setState!

Lifecycle Widget — Siklus Hidup Widget

🌱 Analogi: Siklus Hidup Manusia

🥚 Lahir (createState) → 👶 Bayi (initState) → 🧒 Tumbuh (build) → 🔄 Aktivitas (setState + rebuild) → 🧓 Tua (didUpdateWidget) → 💀 Selesai (dispose)

Flowchart Lifecycle Lengkap

Widget dibuat
createState() — membuat State object
initState() — inisialisasi awal (1× saja)
build() — render UI
▼ ↺ setState? → build() lagi
dispose() — bersihkan resource
Widget dihancurkan
MethodKapan DipanggilBerapa Kali
initState()Saat widget pertama dibuat
build()Setiap state berubahBanyak
dispose()Saat widget dihapus

initState() — Persiapan Sebelum Mulai

👨‍🍳 Analogi: Chef Menyiapkan Dapur

Sebelum memasak, chef menyiapkan bahan, pisau, dan kompor terlebih dahulu. initState() = semua persiapan sebelum widget tampil.

🔪🥕🍳🔥
👨‍🍳 Siapkan semua alat dulu (sekali saja) → initState()

Karakteristik

  • Dipanggil HANYA 1 KALI saat widget pertama dibuat
  • Tempat tepat untuk: load data, setup controller, subscribe stream
  • WAJIB panggil super.initState() di baris pertama
profil_state.dart
class _ProfilState extends State<Profil> {
  late TextEditingController _namaController;
  List<String> daftarItem = [];

  @override
  void initState() {
    super.initState(); // WAJIB baris pertama!

    _namaController = TextEditingController();
    _loadDataDariServer();

    print('initState dipanggil! Hanya 1 kali.');
  }

  Future<void> _loadDataDariServer() async {
    await Future.delayed(Duration(seconds: 2));
    setState(() {
      daftarItem = ['Item 1', 'Item 2', 'Item 3'];
    });
  }
}
📍 Timeline: createState() → initState() → build() → ... → dispose()

build() — Menggambar UI Widget

🎨 Analogi: Pelukis Mengecat Kanvas

Setiap kali state berubah, kanvas dihapus dan dilukis ulang. build() = melukis ulang UI setiap kali state berubah.

🖌️
🎨 Kanvas dilukis ulang tiap state berubah → build()

Penting tentang build()

  • Dipanggil SETIAP KALI setState() dipanggil
  • HARUS return sebuah Widget
  • Harus bersih & cepat (tanpa komputasi berat)
  • JANGAN: network request / DB query di dalam build()
build.dart
@override
Widget build(BuildContext context) {
  // ❌ JANGAN: await fetchDataDariServer();

  // ✓ BENAR — hanya UI logic
  return Scaffold(
    appBar: AppBar(title: Text('Halaman Saya')),
    body: Center(
      child: Column(
        children: [
          Text('Nilai: $nilai'),
          ElevatedButton(
            onPressed: () => setState(() => nilai++),
            child: Text('Tambah'),
          ),
        ],
      ),
    ),
  );
}
⚡ Performance: build() bisa dipanggil RATUSAN kali — jaga agar tetap ringan!

dispose() — Membersihkan Sebelum Pergi

🧹 Analogi: Membersihkan Meja Setelah Bekerja

Karyawan merapikan meja, menutup laptop, mematikan lampu. Jika tidak dibersihkan = memory leak (seperti listrik terus menyala).

🧹
💡💻🗑️
🧹 Bersihkan resource sebelum pergi → dispose()

Kenapa dispose() penting?

  • Mencegah memory leak
  • Menghentikan animation controller
  • Membatalkan subscription stream
  • Membuang TextEditingController
form_state.dart
class _FormState extends State<FormWidget> {
  late TextEditingController _namaController;
  late AnimationController _animController;

  @override
  void initState() {
    super.initState();
    _namaController = TextEditingController();
    _animController = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 300),
    );
  }

  @override
  void dispose() {
    _namaController.dispose();
    _animController.dispose();
    super.dispose(); // WAJIB di baris terakhir
  }
}
🗑️ Checklist dispose: Controller, AnimationController, StreamSubscription, FocusNode — semua WAJIB di-dispose!

Konsep State Management — Siapa Pegang Data?

🏪 Analogi: Gudang Toko Online

Admin update stok di Gudang → Website otomatis update tampilan → Pelanggan selalu lihat data terbaru. Gudang = Single Source of Truth.

Gudang Stok (Source of Truth)
Website (UI) — baca data
Pelanggan lihat data terbaru

3 Konsep Kunci

1. Single Source of Truth

Satu tempat data disimpan

2. Unidirectional Flow

Data mengalir SATU arah

3. Reaktif

UI otomatis update saat data berubah

Data Flow — Bagaimana Data Mengalir

🌊 Analogi: Aliran Sungai Hulu ke Hilir

Air mengalir dari Desa A (parent) → Desa B (child) → Desa C (grandchild). Air tidak bisa mengalir ke atas! Data di Flutter mengalir TOP-DOWN (Parent → Child).

Desa A — Parent
Desa B — Child
Desa C — Grandchild
💧
💧
💧
🌊 Air (data) hanya mengalir ke bawah — TOP-DOWN

3 Cara Passing Data

⬇ Constructor

Passing data turun (parent → child)

⬆ Callback

Passing kejadian naik (child → parent)

🔀 InheritedWidget

Akses banyak widget sekaligus

data_flow.dart
class _ParentWidgetState extends State<ParentWidget> {
  int _nilai = 0;
  void _tambahNilai() => setState(() => _nilai++);

  @override
  Widget build(BuildContext context) {
    return ChildWidget(
      nilai: _nilai,          // Passing data DOWN
      onTambah: _tambahNilai, // Callback UP
    );
  }
}

class ChildWidget extends StatelessWidget {
  final int nilai;
  final VoidCallback onTambah;
  const ChildWidget({required this.nilai, required this.onTambah});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Nilai: $nilai'),
        ElevatedButton(onPressed: onTambah, child: Text('Tambah')),
      ],
    );
  }
}

Rebuild — Kapan UI Digambar Ulang?

📋 Analogi: Papan Pengumuman Sekolah

Admin update pengumuman → papan dicetak ulang. setState() = perintah cetak ulang papan pengumuman.

Pemicu Rebuild

  • setState() dipanggil
  • Parent widget rebuild
  • InheritedWidget berubah
  • Variabel biasa berubah TANPA setState → TIDAK rebuild
setState() dipanggil
Widget ditandai "dirty"
build() dipanggil ulang
Bandingkan tree baru vs lama
Hanya update bagian yang BERBEDA
optimasi.dart
// ✓ LEBIH EFISIEN — gunakan const untuk widget tetap
Widget _buildJudul() => const Text('Halaman Utama'); // const!
💡 Tip: Gunakan const widget untuk mencegah rebuild yang tidak perlu.

Mini Project — Counter App Advanced

Fitur yang Dibuat

  • Counter dengan tombol +, −, dan reset
  • Validasi: tidak bisa kurang dari 0
  • Riwayat perubahan (log counter)
  • Warna berubah berdasarkan nilai
  • Animasi angka saat berubah

Arsitektur App

CounterApp (StatefulWidget) ├── State: _counter, _history, _maxValue ├── Methods: _increment() │ _decrement() │ _reset() └── UI: AppBar + Body + BottomBar

Timeline Pengerjaan: Slide 15 (intro) → 16 (struktur) → 17 (logic) → 18 (UI) → 19 (demo interaktif)

Counter App — Struktur Proyek

Struktur File

counter_advanced/ ├── lib/ │ ├── main.dart ← Entry point │ └── counter_app.dart ← Widget utama └── pubspec.yaml

Widget Hierarchy

MyApp └ MaterialApp └ CounterApp (Stateful) ├ Scaffold │ ├ AppBar │ └ Body │ ├ CounterDisplay │ │ └ AnimatedSwitcher │ └ HistoryList └ BottomBar (− reset +)
state_variables.dart
int _counter = 0;            // Nilai counter saat ini
int _maxValue = 10;          // Batas maksimum
List<String> _history = [];  // Riwayat perubahan
Color _bgColor = Colors.blue; // Warna dinamis

Counter App — State & Logic (Part 1)

counter_app.dart
import 'package:flutter/material.dart';

class CounterApp extends StatefulWidget {
  const CounterApp({Key? key}) : super(key: key);

  @override
  State<CounterApp> createState() => _CounterAppState();
}

class _CounterAppState extends State<CounterApp> {
  int _counter = 0;
  final int _maxValue = 10;
  final int _minValue = 0;
  final List<String> _history = [];
  Color _bgColor = Colors.blue.shade700;

  @override
  void initState() {
    super.initState();
    _history.add('App dimulai. Counter = 0');
  }

  void _increment() {
    if (_counter < _maxValue) {
      setState(() {
        _counter++;
        _updateColor();
        _history.add('Ditambah → Counter = $_counter');
      });
    }
  }

  void _decrement() {
    if (_counter > _minValue) {
      setState(() {
        _counter--;
        _updateColor();
        _history.add('Dikurangi → Counter = $_counter');
      });
    }
  }

  void _reset() {
    setState(() {
      _counter = 0;
      _bgColor = Colors.blue.shade700;
      _history.add('--- Reset! Counter = 0 ---');
    });
  }

  void _updateColor() {
    if (_counter >= 8) {
      _bgColor = Colors.red.shade700;
    } else if (_counter >= 5) {
      _bgColor = Colors.orange.shade700;
    } else {
      _bgColor = Colors.blue.shade700;
    }
  }

  @override
  void dispose() {
    _history.clear();
    super.dispose();
  }
📌 Catatan: Setiap perubahan state dibungkus setState() agar UI ikut update. Validasi _minValue/_maxValue mencegah nilai keluar batas.

Counter App — Membangun UI (Part 2)

counter_app.dart (build)
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: _bgColor,
      appBar: AppBar(
        title: const Text('Counter Advanced'),
        backgroundColor: Colors.transparent,
      ),
      body: Column(
        children: [
          Expanded(
            flex: 2,
            child: Center(
              child: AnimatedSwitcher(
                duration: Duration(milliseconds: 300),
                child: Text(
                  '$_counter',
                  key: ValueKey<int>(_counter),
                  style: TextStyle(
                    fontSize: 120,
                    fontWeight: FontWeight.bold,
                    color: Colors.white,
                  ),
                ),
              ),
            ),
          ),
          Padding(
            padding: EdgeInsets.symmetric(horizontal: 24),
            child: LinearProgressIndicator(
              value: _counter / _maxValue,
              backgroundColor: Colors.white30,
              valueColor: AlwaysStoppedAnimation(Colors.white),
            ),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              FloatingActionButton(
                onPressed: _decrement,
                child: Icon(Icons.remove),
              ),
              FloatingActionButton(
                onPressed: _reset,
                child: Icon(Icons.refresh),
              ),
              FloatingActionButton(
                onPressed: _increment,
                child: Icon(Icons.add),
              ),
            ],
          ),
          Expanded(
            flex: 1,
            child: ListView.builder(
              itemCount: _history.length,
              itemBuilder: (ctx, i) => ListTile(
                title: Text(_history[i]),
              ),
            ),
          ),
        ],
      ),
    );
  }
}
AnimatedSwitcher

Animasi transisi saat angka berganti

LinearProgressIndicator

Bar progres counter / maxValue

key: ValueKey

Memberitahu Flutter widget berubah

Demo — Counter App Advanced

Coba tombol −, reset, dan + di bawah ini! (maks 10, min 0)

Counter Advanced
0

Ringkasan — Apa yang Sudah Dipelajari?

STATE MANAGEMENT DASAR ├─ Stateless Widget → "Foto dinding (tetap)" ├─ Stateful Widget → "Remote TV (berubah)" ├─ setState() → "Papan tulis (tulis ulang)" ├─ Lifecycle │ ├─ initState → "Siapkan dapur" │ ├─ build → "Lukis kanvas" │ └─ dispose → "Bersihkan meja" ├─ Data Flow → "Aliran sungai (top-down)" ├─ Rebuild → "Cetak ulang papan" └─ Mini Project → "Counter App Advanced"

1️⃣

Gunakan StatefulWidget ketika widget perlu berubah

2️⃣

Selalu gunakan setState() untuk update state

3️⃣

Jangan lupa dispose() resource dari initState()

🎉 BAB 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 ✓ SELESAI!

Latihan 1 — Stateless atau Stateful?

Pilih jenis widget yang paling tepat, lalu periksa jawaban

1. Widget menampilkan teks 'Selamat Datang' yang tidak pernah berubah
StatelessWidgetStatefulWidget
✓ Stateless — tidak ada data yang berubah.
2. Widget counter yang bertambah saat tombol diklik
StatelessWidgetStatefulWidget
✓ Stateful — nilai berubah, butuh setState().
3. Widget ikon yang selalu sama
StatelessWidgetStatefulWidget
✓ Stateless — ikon statis.
4. Widget form input nama dengan validasi realtime
StatelessWidgetStatefulWidget
✓ Stateful — input & validasi berubah-ubah.
5. Widget menampilkan foto profil dari data yang diterima
StatelessWidgetStatefulWidget
✓ Stateless — hanya menampilkan data dari parameter.

Latihan 2 — Lengkapi Kode Berikut!

Isi bagian ___ agar Counter bekerja

latihan.dart
class _SimpleCounterState extends ___<SimpleCounter> {
  int _nilai = 0;

  @override
  void ___() {
    super.initState();
    print('Counter dimulai!');
  }

  void _tambah() {
    ___(() {
      _nilai++;
    });
  }

  @override
  Widget ___(___ context) {
    return Column(
      children: [
        Text('Nilai: $_nilai'),
        ElevatedButton(
          onPressed: ___,
          child: Text('Tambah'),
        ),
      ],
    );
  }

  @override
  void ___() {
    print('Widget dihancurkan!');
    super.dispose();
  }
}

Kunci Jawaban (urut)

  • State <SimpleCounter>
  • initState
  • setState
  • build ( BuildContext context )
  • _tambah (callback onPressed)
  • dispose

Tugas Akhir — Mini Shopping Cart

Deskripsi Tugas

  • Daftar produk (min 4, nama & harga)
  • Tombol "Tambah ke Keranjang" tiap produk
  • Counter jumlah item di AppBar
  • Total harga berubah realtime
  • Tombol "Kosongkan Keranjang"

Wireframe

┌────────────────────────┐ │ 🛒 Toko Online [🛒 3] │ ├────────────────────────┤ │ Produk A — Rp15.000 │ │ [+ Tambah ke Keranjang] │ │ Produk B — Rp25.000 │ │ [+ Tambah ke Keranjang] │ ├────────────────────────┤ │ Total: Rp80.000 │ │ [🗑 Kosongkan Keranjang] │ └────────────────────────┘

Rubrik Penilaian (100 poin)

KriteriaPoin
State variables benar & lengkap25
setState() digunakan dengan benar25
UI berubah sesuai state25
Validasi (tidak bisa < 0)15
Kode rapi & ada komentar10
hint.dart
List<Map> produk = [];    // daftar produk
List<Map> keranjang = []; // isi keranjang
int totalItem = 0;        // jumlah item
int totalHarga = 0;       // total harga (realtime)
⏰ Kumpulkan Minggu Depan!