Mobiele ontwikkeling met Flutter · Les

Events, statussen en de keuze tussen Cubit en Bloc

Kies tussen Cubit en Bloc en ontwerp overzichtelijke transformaties van events naar statussen.

Les 1 van 413 stappen

Events, statussen en de keuze tussen Cubit en Bloc is een gratis Mobiele ontwikkeling met Flutter-les op CoddyKit. Dit is les 1 van 4. Je kunt 3 lessen uit dit leerpad gratis volledig lezen — daarna ontgrendelt CoddyKit PRO alle lessen, plus praktische oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Mobiele ontwikkeling met Flutter. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Mobiele ontwikkeling met Flutter bevat in totaal 4 lessen.

Twee hulpmiddelen, één familie

Het pakket flutter_bloc levert twee bouwstenen voor toestandsbeheer: Cubit en Bloc. Beide breiden dezelfde basisklasse uit en sturen allebei een stroom toestanden naar je gebruikersinterface.

  • Cubit stelt gewone methoden beschikbaar die je rechtstreeks aanroept (bijvoorbeeld increment()).
  • Bloc reageert op events die je toevoegt (bijvoorbeeld add(IncrementPressed())) en vertaalt deze naar toestanden.

In deze les leer je hoe elk van beide invoer omzet in State en hoe je voor een bepaalde functie de juiste kiest.

Cubit: van methoden naar toestanden

Een Cubit is de eenvoudigere primitive. Je breidt Cubit<T> uit, geeft een beginstatus door aan super(...) en roept binnen methoden emit(newState) aan om een nieuwe status naar luisteraars te sturen.

Er is geen gebeurtenisobject en geen toewijzingslaag. De methode is het API-oppervlak.

import 'package:flutter_bloc/flutter_bloc.dart';

class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);

  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
  void reset() => emit(0);
}

Bloc: Van gebeurtenissen naar toestanden

Een Bloc scheidt de intentie (een gebeurtenis) van de logica (een afhandelaar). Je definieert gebeurtenisklassen en registreert vervolgens afhandelaars met on<Event> in de constructor. De gebruikersinterface roept de logica nooit rechtstreeks aan; die voegt alleen gebeurtenissen toe met add.

Deze indirecte laag zorgt voor meer standaardcode, maar geeft je wel één traceerbaar kanaal voor elke statuswijziging.

import 'package:flutter_bloc/flutter_bloc.dart';

sealed class CounterEvent {}

class IncrementPressed extends CounterEvent {}
class DecrementPressed extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<IncrementPressed>((event, emit) => emit(state + 1));
    on<DecrementPressed>((event, emit) => emit(state - 1));
  }
}

Toestanden expliciet modelleren

Een gewone int werkt voor een teller, maar echte functies hebben meerdere vormen: laden, geslaagd en mislukt. Modelleer deze als een afgesloten klassenhiërarchie, zodat de compiler je verplicht om elk geval in de gebruikersinterface af te handelen.

  • Afgesloten klassen maken uitputtende switch-instructies mogelijk in Dart 3.
  • Elke toestand bevat alleen de gegevens die voor die fase geldig zijn.
sealed class ProfileState {
  const ProfileState();
}

class ProfileLoading extends ProfileState {
  const ProfileLoading();
}

class ProfileLoaded extends ProfileState {
  final String name;
  const ProfileLoaded(this.name);
}

class ProfileError extends ProfileState {
  final String message;
  const ProfileError(this.message);
}

void main() {
  final ProfileState s = ProfileLoaded('Ada');
  final label = switch (s) {
    ProfileLoading() => 'Loading...',
    ProfileLoaded(:final name) => 'Hello, $name',
    ProfileError(:final message) => 'Error: $message',
  };
  print(label);
}

Equatable: onnodige opnieuw opbouwen voorkomen

Bloc en Cubit brengen luisteraars alleen op de hoogte wanneer de nieuwe toestand niet gelijk is aan de vorige. Standaard vergelijken Dart-objecten op identiteit, dus twee afzonderlijke instanties met dezelfde gegevens worden als verschillend beschouwd en starten opnieuw opbouwen.

Overschrijf gelijkheid (vaak met het pakket equatable), zodat toestanden met gelijke waarden worden samengevoegd en je widgets niet onnodig opnieuw worden opgebouwd.

import 'package:equatable/equatable.dart';

class CartState extends Equatable {
  final int itemCount;
  final double total;
  const CartState(this.itemCount, this.total);

  @override
  List<Object?> get props => [itemCount, total];
}

// emit(CartState(2, 19.98)) twice in a row notifies listeners only once.

Asynchroon werk binnen een afhandelaar

De meeste echte afhandelaars zijn asynchroon: haal gegevens op en stuur daarna een toestand uit. Met een Bloc kun je vanuit één afhandelaar meerdere keren een toestand uitsturen — eerst een laadstatus en daarna een geslaagde of mislukte status.

De handtekening van de afhandelaar geeft je daarom een emit-callback in plaats van een retourwaarde: zo kun je tijdens één gebeurtenis meerdere toestanden doorgeven.

class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
  final ProfileRepo repo;
  ProfileBloc(this.repo) : super(const ProfileLoading()) {
    on<ProfileRequested>((event, emit) async {
      emit(const ProfileLoading());
      try {
        final name = await repo.fetchName(event.id);
        emit(ProfileLoaded(name));
      } catch (e) {
        emit(ProfileError(e.toString()));
      }
    });
  }
}

Gebeurtenistransformators: de exclusieve superkracht van Bloc

Dit is de functie die de keuze het vaakst bepaalt. Met een Bloc bepaal je hoe gelijktijdige gebeurtenissen worden verwerkt door een transformer door te geven aan on<Event> (uit het pakket bloc_concurrency):

  • concurrent() — verwerk alle gebeurtenissen parallel (standaard).
  • sequential() — één voor één, in volgorde.
  • droppable() — negeer nieuwe gebeurtenissen zolang er één wordt verwerkt (ideaal voor knoppen).
  • restartable() — annuleer de actieve afhandelaar zodra er een nieuwere gebeurtenis binnenkomt (ideaal voor zoeken).

Cubit heeft geen gebeurtenisstroom en kan dit dus niet doen zonder de logica voor debouncing of throttling zelf te schrijven.

import 'package:bloc_concurrency/bloc_concurrency.dart';
import 'package:stream_transform/stream_transform.dart';

EventTransformer<E> debounce<E>(Duration d) {
  return (events, mapper) => events.debounce(d).switchMap(mapper);
}

class SearchBloc extends Bloc<SearchEvent, SearchState> {
  SearchBloc() : super(const SearchInitial()) {
    on<QueryChanged>(
      _onQueryChanged,
      transformer: debounce(const Duration(milliseconds: 300)),
    );
  }

  Future<void> _onQueryChanged(QueryChanged e, Emitter emit) async {
    // runs at most once per 300ms of typing
  }
}

Overgangen observeren voor foutopsporing

Omdat een Bloc elke wijziging via gebeurtenissen leidt, kan het een volledige Transition rapporteren: de huidige toestand, de gebeurtenis die de wijziging veroorzaakte en de volgende toestand. Overschrijf onTransition (of gebruik een globale BlocObserver) om dit kanaal in een logboek vast te leggen.

Een Cubit ziet alleen een Change (huidige en volgende toestand) — er is geen gebeurtenis om vast te leggen, omdat er geen gebeurtenis is. Daarom heeft Bloc de voorkeur voor functies waarvoor een audittrail nodig is.

import 'package:flutter_bloc/flutter_bloc.dart';

class AppObserver extends BlocObserver {
  @override
  void onTransition(Bloc bloc, Transition transition) {
    super.onTransition(bloc, transition);
    print('${bloc.runtimeType}: ${transition.event} '
        '=> ${transition.nextState}');
  }
}

void main() {
  Bloc.observer = AppObserver();
}

De beslisregel

Een praktische vuistregel die de beheerders van Bloc zelf aanraden: begin met een Cubit en kies pas voor een Bloc wanneer je iets nodig hebt wat Bloc toevoegt.

Kies Cubit wanneer:

  • De logica bestaat uit eenvoudige, rechtstreekse methodeaanroepen (schakelaars, tellers en formuliervelden).
  • Je gelijktijdige invoer niet hoeft te vertragen, te beperken of te negeren.
  • Je minder standaardcode en een eenvoudigere kennismaking belangrijk vindt.

Kies Bloc wanneer:

  • Je gebeurtenistransformators nodig hebt (zoekopdrachten vertragen, verzenden negeerbaar maken).
  • Je een traceerbaar gebeurtenissenlogboek wilt voor analyse of foutopsporing.
  • Veel verschillende invoerwaarden naar één functie worden vertaald en je deze als gebeurtenistypen wilt documenteren.

Dezelfde functie, op beide manieren

Vergelijk een schakelaar die als Cubit en als Bloc is geschreven. De Cubit is korter en leest van boven naar beneden; de Bloc voegt een gebeurtenistype en een afhandelaar toe. Voor een eenvoudige schakelaar is de Cubit de betere keuze — de Bloc-infrastructuur levert hier niets op.

// Cubit version
class ThemeCubit extends Cubit<bool> {
  ThemeCubit() : super(false);
  void toggle() => emit(!state);
}

// Bloc version (same behavior, more ceremony)
sealed class ThemeEvent {}
class ThemeToggled extends ThemeEvent {}

class ThemeBloc extends Bloc<ThemeEvent, bool> {
  ThemeBloc() : super(false) {
    on<ThemeToggled>((e, emit) => emit(!state));
  }
}

Schone transformaties van gebeurtenis naar toestand ontwerpen

Welke je ook kiest, houd transformaties overzichtelijk:

  • Toestanden zijn onveranderlijk; gebruik copyWith om de volgende toestand af te leiden in plaats van de huidige te wijzigen.
  • Eén gebeurtenis (of methode) moet worden vertaald naar een samenhangende reeks uitgestuurde toestanden, nooit naar navigatie in de gebruikersinterface of neveneffecten die je niet kunt traceren.
  • Houd I/O in een repository; de Bloc/Cubit coördineert alleen en stuurt toestanden uit.
class FormState {
  final String email;
  final bool submitting;
  const FormState({this.email = '', this.submitting = false});

  FormState copyWith({String? email, bool? submitting}) => FormState(
        email: email ?? this.email,
        submitting: submitting ?? this.submitting,
      );
}

void main() {
  const start = FormState();
  final next = start.copyWith(submitting: true);
  print('${next.email}|${next.submitting}'); // |true
}

Korte controle

Een zoekveld moet tijdens het typen van de gebruiker een netwerkverzoek versturen, maar pas nadat de gebruiker 300 ms heeft gepauzeerd. Ook moet elk actief verzoek worden geannuleerd zodra er een nieuw teken wordt ingevoerd. Welke keuze past hier het best bij en waarom?

Samenvatting

Je hebt geleerd hoe elke primitive invoer omzet in een toestand en hoe je een keuze maakt:

  • Cubit = methoden roepen rechtstreeks emit aan. Minder standaardcode; ideaal voor schakelaars, tellers en eenvoudige formulieren.
  • Bloc = gebeurtenissen worden met add toegevoegd en via on<Event> vertaald. Je krijgt gebeurtenistransformators (debounce/throttle/droppable/restartable) en een traceerbaar Transition-logboek.
  • Modelleer toestanden als afgesloten, onveranderlijke klassen; gebruik Equatable om opnieuw opbouwen te voorkomen en copyWith om volgende toestanden af te leiden.
  • Vuistregel: begin met een Cubit en stap alleen over naar een Bloc wanneer je besturing van gelijktijdigheid of een audittrail van gebeurtenissen nodig hebt.
Gratis beginnen

Leer Dart met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
22
Lessen
88

Veelgestelde vragen

Is de les “Events, statussen en de keuze tussen Cubit en Bloc” gratis?

Ja — je kunt hier op het web alle 3 lessen van het leerpad Mobiele ontwikkeling met Flutter, waaronder “Events, statussen en de keuze tussen Cubit en Bloc”, gratis volledig lezen. Daarna ontgrendelt CoddyKit PRO alle lessen, plus interactieve oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. De cursus Mobiele ontwikkeling met Flutter bevat in totaal 4 lessen.

Wat leer ik in “Events, statussen en de keuze tussen Cubit en Bloc”?

Kies tussen Cubit en Bloc en ontwerp overzichtelijke transformaties van events naar statussen. Je oefent met Mobiele ontwikkeling met Flutter door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met Mobiele ontwikkeling met Flutter te beginnen?

Ervaring vooraf is niet nodig. Mobiele ontwikkeling met Flutter op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 1 van 4.

Hoe lang duurt de les “Events, statussen en de keuze tussen Cubit en Bloc”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over Mobiele ontwikkeling met Flutter?

Ja. Elke les over Mobiele ontwikkeling met Flutter bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. Events, statussen en de keuze tussen Cubit en Bloc
  2. Streamtransformators en event-debouncing in BLoC
  3. State persistent maken met HydratedBloc
  4. BLoC's testen met bloc_test en Mocktail
← Terug naar Mobiele ontwikkeling met Flutter