blocstatearsitekturMahir5 mnt baca

BLoC: Konsep Event-State

Pola BLoC: event masuk, state keluar. Kapan memilih BLoC.

Masalah yang Diselesaikan BLoC

Di aplikasi kecil, setState cukup. Tapi di aplikasi besar yang dikerjakan tim, pertanyaan paling menyebalkan saat debugging adalah "state ini berubah karena apa, dari mana, kapan?" Kalau state bisa diubah dari mana pun, bug seperti logout misterius hampir mustahil dilacak.

BLoC menjawab dengan satu aturan keras: UI tidak boleh mengubah state langsung. UI hanya mengirim event ("tombol bayar ditekan"); BLoC memprosesnya dan memancarkan state baru ("pembayaran sukses"). Semua perubahan mengalir lewat satu pintu yang eksplisit dan bisa ditest.

Alur yang searah membuat tiap transisi bisa dicatat (BlocObserver), di-replay, dan di-unit-test sebagai Dart murni. Pola ini dipopulerkan package bloc karya Felix Angelov dan dipakai banyak aplikasi produksi besar.

Tiga Komponen: Event, State, Bloc

UI --Event--> BLoC --State--> UI
  1. Event: sesuatu yang terjadi (LoginDiminta, BayarDitekan). Fakta tentang aksi user, bukan perintah ke UI.
  2. State: kondisi pada satu titik waktu (LoginAwal, LoginLoading, LoginSukses(token)). UI me-render murni dari state.
  3. Bloc: fungsi yang memetakan event menjadi aliran state. Di sinilah validasi, pemanggilan repository, dan percabangan async tinggal.

Sketsa counter yang sengaja eksplisit:

dart
sealed class CounterEvent {} // apa yang terjadi
class CounterDitambah extends CounterEvent {}
class CounterDikurang extends CounterEvent {}
class CounterDireset extends CounterEvent {}

class CounterState { // kondisi saat ini
  final int nilai;
  const CounterState(this.nilai);
}

sealed class membuat compiler tahu semua subtype, sehingga switch yang lupa satu cabang ditolak saat compile: cabang terlupakan menjadi compile error, bukan bug runtime.

Contoh Nyata: Alur Login

Login adalah contoh klasik: banyak cabang (idle, loading, sukses, gagal), tiap cabang me-render UI berbeda.

dart
// State
sealed class LoginState {}
class LoginAwal extends LoginState {}
class LoginLoading extends LoginState {}
class LoginSukses extends LoginState {
  final String token;
  LoginSukses(this.token);
}
class LoginGagal extends LoginState {
  final String pesan;
  LoginGagal(this.pesan);
}

// Bloc: event masuk, state keluar.
class LoginBloc extends Bloc<LoginEvent, LoginState> {
  final AuthRepo repo;
  LoginBloc(this.repo) : super(LoginAwal()) {
    on<LoginDiminta>((event, emit) async {
      emit(LoginLoading());
      try {
        emit(LoginSukses(await repo.login(event.email, event.password)));
      } catch (_) {
        emit(LoginGagal('Email atau kata sandi salah'));
      }
    });
  }
}

Di UI, BlocBuilder tinggal switch state: form saat LoginAwal, spinner saat LoginLoading, pindah halaman saat LoginSukses (via BlocListener), snackbar saat LoginGagal. Tanpa flag isLoading yang dijaga manual.

Edge Case dan Praktik Terbaik

  • Event bertubi-tubi. Mengetik di kolom pencarian mengirim puluhan event/detik; request lama bisa menimpa hasil baru (race condition). Pakai event transformer dari bloc_concurrency (restartable(), droppable()) atau debounce.
  • Jangan emit state yang setara dengan saat ini. BLoC membandingkan dengan ==; tanpa override ==, tiap emit dianggap berubah dan UI rebuild sia-sia. Pakai equatable atau freezed.
  • Jangan simpan BuildContext di Bloc. Bloc adalah Dart murni. Butuh navigasi? Pancarkan state, biarkan BlocListener di UI yang bereaksi.
  • Satu Bloc per fitur. AppBloc raksasa dengan 50 event adalah code smell; pecah: LoginBloc, KeranjangBloc, CheckoutBloc.
  • Jangan biarkan exception lolos tanpa state. Bungkus tiap await dengan try/catch yang meng-emit state error. UI butuh cabang error eksplisit.

Kapan Tidak Pakai BLoC

  • State sederhana (toggle tema, counter, tab aktif): boilerplate tiga class event/state/bloc terlalu mahal. Pakai Cubit atau Riverpod.
  • Prototype cepat/project solo kecil: kecepatan iterasi lebih penting. BLoC bersinar saat kode berumur panjang dan disentuh banyak orang.
  • State lokal satu widget (isi form): setState cukup. Jangan angkat ke Bloc hanya karena "biar rapi".

Catatan teknis: BLoC memaksa pemisahan ketat antara UI dan logika bisnis. Biaya di awal adalah boilerplate yang lebih banyak, tapi terbayar lunas di project besar: bug lebih mudah dilacak, code review lebih mudah karena event dan state mendokumentasikan diri sendiri, dan logika bisa ditest tanpa emulator.

Tantangan

Rancang event-state

Untuk fitur login, tulis daftar event (LoginDiminta, LogoutDiminta) dan state (LoginAwal, LoginLoading, LoginSukses, LoginGagal) yang dibutuhkan. Tanpa kode BLoC penuh, cukup rancangannya.