flutterblocstate-managementMahir3 mnt baca

Pengenalan BLoC Pattern

Pahami arsitektur BLoC: pisahkan event, state, dan logic bisnis dengan Cubit yang simpel lalu Bloc penuh.

Filosofi BLoC

BLoC (Business Logic Component) memisahkan tiga hal dengan tegas: Event (apa yang terjadi), State (kondisi saat ini), dan Bloc (aturan yang mengubah event menjadi state baru). UI hanya mengirim event dan me-render state. Logic bisnis tidak bocor ke widget.

Mulai dari Cubit: BLoC yang simpel

Cubit adalah versi sederhana tanpa event class. Cocok untuk dipelajari dulu sebelum Bloc penuh.

yaml
dependencies:
  flutter_bloc: ^8.1.0
dart
import 'package:flutter_bloc/flutter_bloc.dart';

// 1. Cubit: menyimpan state int
class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);

  void tambah() => emit(state + 1);
  void kurang() => emit(state - 1);
  void reset() => emit(0);
}

// 2. Sediakan ke widget tree
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => CounterCubit(),
      child: const MaterialApp(home: HalamanCounter()),
    );
  }
}

// 3. Konsumsi di UI
class HalamanCounter extends StatelessWidget {
  const HalamanCounter({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Cubit Counter')),
      body: Center(
        child: BlocBuilder<CounterCubit, int>(
          builder: (context, angka) => Text(
            '$angka',
            style: const TextStyle(fontSize: 48),
          ),
        ),
      ),
      floatingActionButton: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          FloatingActionButton(
            heroTag: 'tambah',
            onPressed: () => context.read<CounterCubit>().tambah(),
            child: const Icon(Icons.add),
          ),
          const SizedBox(height: 8),
          FloatingActionButton(
            heroTag: 'kurang',
            onPressed: () => context.read<CounterCubit>().kurang(),
            child: const Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

BlocBuilder rebuild saat state berubah, context.read memanggil method tanpa berlangganan, mirip ref.read di Riverpod.

Naik ke Bloc: dengan Event

Untuk logic kompleks, Bloc memakai event class agar setiap aksi terdokumentasi:

dart
// Event
sealed class KasirEvent {}
class TambahItem extends KasirEvent {
  final String nama;
  TambahItem(this.nama);
}
class HapusItem extends KasirEvent {
  final String nama;
  HapusItem(this.nama);
}

// Bloc
class KasirBloc extends Bloc<KasirEvent, List<String>> {
  KasirBloc() : super([]) {
    on<TambahItem>((event, emit) => emit([...state, event.nama]));
    on<HapusItem>((event, emit) {
      emit(state.where((p) => p != event.nama).toList());
    });
  }
}

// Mengirim event dari UI
context.read<KasirBloc>().add(TambahItem('Kopi Tubruk'));

Event class membuat alur data eksplisit dan mudah di-test: beri event X, harapkan state Y.

BlocListener untuk efek samping

Navigasi, snackbar, atau dialog tidak boleh di BlocBuilder (yang bisa rebuild berkali-kali). Pakai BlocListener:

dart
BlocListener<AuthBloc, AuthState>(
  listener: (context, state) {
    if (state is AuthSukses) {
      Navigator.pushReplacementNamed(context, '/');
    } else if (state is AuthGagal) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(state.pesan)),
      );
    }
  },
  child: const FormLogin(),
)

Catatan teknis: Jangan membuat Bloc/Cubit di dalam method build karena build bisa dipanggil berkali-kali dan kamu akan membuat instance baru sambil membuang yang lama (beserta state-nya). Selalu buat di BlocProvider(create: ...) di atas widget yang memakainya. Dan tutup stream manual kalau Bloc memakai subscription sendiri, lewat override close().