BLoC: Cubit (Versi Simpel)
Cubit: panggil method, state berubah. Tanpa event.
Masalah yang Diselesaikan Cubit
BLoC penuh itu tegas, tapi untuk state sederhana terasa seperti birokrasi: tiga class hanya untuk sebuah counter. Padahal ~80% state aplikasi (toggle tema, counter, todo, keranjang) hanya butuh "panggil method, state berubah".
Cubit adalah BLoC tanpa event: langsung cubit.tambah(), tanpa class CounterDitambah. Satu file, satu class, dengan semua keuntungan BLoC: aliran state searah, mudah ditest, terpisah dari UI.
Catatan teknis: Di balik layar,
CubitdanBlocmemakai mesin yang sama.Blocpada dasarnya adalahCubityang method-nya dipicu oleh event. Jadi naik dari Cubit ke Bloc di kemudian hari tidak butuh rewrite total.
Contoh Dasar
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0); // state awal
void tambah() => emit(state + 1);
void kurang() => emit(state - 1);
void reset() => emit(0);
}emit memancarkan state baru ke semua listener. Tanpa event class, tanpa on<Event>.
Setup dan pemakaian di UI:
// 1. Tambah dependency: flutter pub add flutter_bloc
// 2. Sediakan cubit di atas widget yang membutuhkan:
BlocProvider(
create: (_) => CounterCubit(),
child: const CounterPage(),
),
// 3a. Tampilkan (rebuild otomatis saat state berubah):
BlocBuilder<CounterCubit, int>(
builder: (context, count) => Text('$count'),
),
// 3b. Panggil method dari event handler (tanpa rebuild):
onPressed: () => context.read<CounterCubit>().tambah(),Perhatikan pemisahan yang sama seperti di Riverpod: tampilkan pakai builder, aksi pakai read di dalam callback.
Contoh Nyata: TodoCubit
Contoh yang lebih nyata: todo dengan tambah, toggle selesai, dan hapus yang selesai.
class Todo {
final String id;
final String judul;
final bool selesai;
const Todo({required this.id, required this.judul, this.selesai = false});
Todo copyWith({bool? selesai}) => Todo(
id: id,
judul: judul,
selesai: selesai ?? this.selesai,
);
}
class TodoCubit extends Cubit<List<Todo>> {
TodoCubit() : super(const []);
void tambah(String judul) => emit([
...state,
Todo(id: DateTime.now().toIso8601String(), judul: judul),
]);
void toggle(String id) => emit([
for (final t in state)
if (t.id == id) t.copyWith(selesai: !t.selesai) else t,
]);
void hapusSelesai() =>
emit(state.where((t) => !t.selesai).toList());
}Di UI, BlocBuilder<TodoCubit, List<Todo>> me-render ListView; tiap baris: Checkbox(onChanged: (_) => context.read<TodoCubit>().toggle(todo.id)). Seluruh logika terkonsentrasi di satu class yang testable tanpa widget.
Untuk efek samping (snackbar "Todo ditambahkan"), pakai BlocListener/BlocConsumer: builder untuk render, listener untuk aksi. Jangan taruh ScaffoldMessenger di builder yang berjalan berkali-kali.
Edge Case dan Praktik Terbaik
- Jangan mutasi lalu emit instance yang sama.
state.add(todo); emit(state);adalah bug klasik: BLoC membandingkan dengan==, object yang sama berarti UI tidak rebuild. Selalu buat list baru, seperti aturan immutability Riverpod. - Jangan emit setelah cubit di-close. Operasi async yang selesai setelah halaman di-pop membuat
emitmelempar error. Cekif (isClosed) return;setelahawait. - Bungkus async dengan try/catch. State
List<Todo>tak punya tempat untuk error; untuk kasus serius, naikkan ke sealed class atau pindah ke Bloc penuh. - Pahami scope
BlocProvider. Cubit daricreatedi-closeotomatis saat dispose. Dua layar butuh cubit yang sama? PakaiBlocProvider.value, jangan buat dua kali. - Testing itu ekspresif dengan
bloc_test:
blocTest<TodoCubit, List<Todo>>(
'menambah todo',
build: () => TodoCubit(),
act: (cubit) => cubit.tambah('Beli kopi'),
expect: () => [
[isA<Todo>().having((t) => t.judul, 'judul', 'Beli kopi')],
],
);Kapan Tidak Pakai Cubit
- Naik ke Bloc kalau butuh melacak tiap aksi user (audit trail) atau event transformer (debounce/cancel). Itulah gunanya class event.
- Turun ke
setState/ValueNotifierkalau state hanya hidup di satu widget kecil dan tidak dipakai di tempat lain. Tidak semua state perlu dibawa keluar widget. - Pertimbangkan Riverpod kalau project sudah memakainya dan butuh DI antar provider (
ref.watchdi notifier). Cubit hidup di duniaBuildContext, bukanref.
Tantangan
Cubit tema
Buat ThemeCubit extends Cubit<bool> (false = terang) dengan method toggle(). UI: Switch yang watch state via BlocBuilder dan memanggil toggle() saat diubah.