Events, states og valget mellem Cubit og Bloc
Vælg mellem Cubit og Bloc, og design rene transformationer fra events til states.
Events, states og valget mellem Cubit og Bloc er en gratis Mobiludvikling med Flutter-lektion på CoddyKit. Dette er lektion 1 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i Mobiludvikling med Flutter, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Mobiludvikling med Flutter-kurset indeholder 4 lektioner i alt.
To værktøjer, én familie
Pakken flutter_bloc indeholder to primitiver til tilstandsstyring: Cubit og Bloc. Begge nedarver fra den samme basisklasse, og begge udsender en strøm af tilstande til din brugergrænseflade.
- Cubit viser almindelige metoder, som du kalder direkte (for eksempel
increment()). - Bloc reagerer på hændelser, som du tilføjer (for eksempel
add(IncrementPressed())), og omsætter dem til tilstande.
I denne lektion lærer du, hvordan hver af dem omdanner input til State, og hvordan du vælger den rigtige til en given funktion.
Cubit: Fra metoder til tilstande
En Cubit er den enklere primitive. Du udvider Cubit<T>, sender en begyndelsestilstand til super(...) og kalder emit(newState) i metoder for at sende en ny tilstand til lyttere.
Der er intet begivenhedsobjekt og intet mappingslag. Metoden er API-overfladen.
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: Fra begivenheder til tilstande
En Bloc adskiller hensigten (en begivenhed) fra logikken (en behandler). Du definerer begivenhedsklasser og registrerer derefter behandlere med on<Event> i konstruktøren. Brugergrænsefladen kalder aldrig logikken direkte; den tilføjer kun begivenheder med add.
Denne indirektion kræver mere standardkode, men giver dig en samlet, sporbar kanal for hver ændring af tilstanden.
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));
}
}Modellering af tilstande eksplicit
En almindelig int fungerer til en tæller, men virkelige funktioner har flere former: indlæsning, succes og fejl. Modellér disse som et hierarki af sealed-klasser, så compileren tvinger brugergrænsefladen til at håndtere alle tilfælde.
- Sealed-klasser muliggør udtømmende
switchi Dart 3. - Hver tilstand indeholder kun de data, der er gyldige i den pågældende fase.
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: Undgå overflødige genopbygninger
Bloc og Cubit underretter kun lyttere, når den nye tilstand ikke er lig med den forrige. Som standard sammenligner Dart-objekter identitet, så to forskellige instanser med de samme data behandles som forskellige og udløser en genopbygning.
Overskriv lighed (ofte med pakken equatable), så tilstande med samme værdi deduplikeres, og dine widgets holder op med at blive genopbygget unødvendigt.
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.Asynkront arbejde i en behandler
De fleste virkelige behandlere er asynkrone: hent data, og udsend derefter en tilstand. Med en Bloc kan du udsende flere gange fra én behandler – først en indlæsningstilstand og derefter succes eller fejl.
Behandlersignaturen giver dig et emit-callback i stedet for en returværdi, netop så du kan strømme flere tilstande igennem under én begivenhed.
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()));
}
});
}
}Begivenhedstransformere: Blocs særlige superkraft
Dette er den funktion, der oftest afgør valget. Med en Bloc styrer du hvordan samtidige begivenheder behandles ved at sende en transformer til on<Event> (fra pakken bloc_concurrency):
concurrent()— håndter alle begivenheder parallelt (standard).sequential()— én ad gangen og i rækkefølge.droppable()— ignorér nye begivenheder, mens én kører (godt til knapper).restartable()— annullér den igangværende behandler, når en nyere begivenhed ankommer (godt til søgning).
Cubit har ingen begivenhedsstrøm, så den kan ikke gøre dette uden at skrive debounce-/throttle-logikken manuelt.
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
}
}Observer overgange til fejlfinding
Fordi en Bloc fører enhver ændring gennem begivenheder, kan den rapportere en fuldstændig Transition: den aktuelle tilstand, den udløsende begivenhed og den næste tilstand. Overskriv onTransition (eller brug en global BlocObserver) for at logge denne kanal.
En Cubit ser kun Change (aktuel og næste tilstand) – der er ingen begivenhed at logge, fordi der ikke er nogen begivenhed. Derfor foretrækkes Bloc til funktioner, der har brug for et revisionsspor.
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();
}Beslutningsreglen
En praktisk tommelfingerregel, som vedligeholderne af Bloc selv anbefaler: Begynd med en Cubit, og vælg først en Bloc, når du har brug for det, som en Bloc tilføjer.
Vælg Cubit, når:
- Logikken består af enkle, direkte metodekald (skift, tællere og formularfelter).
- Du ikke har brug for at debouncere, throttle eller bortkaste samtidige input.
- Du sætter pris på mindre standardkode og lettere introduktion.
Vælg Bloc, når:
- Du har brug for begivenhedstransformere (debounce af søgning, bortkastning af indsendelser).
- Du ønsker en sporbar begivenhedslog til analyse eller fejlfinding.
- Mange forskellige input knytter sig til én funktion, og du ønsker dem dokumenteret som begivenhedstyper.
Samme funktion på begge måder
Sammenlign et skift skrevet som en Cubit med en Bloc. Cubit er kortere og læses fra top til bund; Bloc tilføjer en begivenhedstype og en behandler. Til et rent skift er Cubit det bedre valg – Bloc-mekanikken giver dig ikke noget her.
// 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));
}
}Design af rene transformationer fra begivenhed til tilstand
Uanset hvad du vælger, skal du holde transformationerne rene:
- Tilstande er uforanderlige; brug
copyWithtil at udlede den næste tilstand i stedet for at ændre den. - Én begivenhed (eller metode) bør knytte sig til et sammenhængende sæt udsendte tilstande, aldrig til navigation i brugergrænsefladen eller bivirkninger, du ikke kan spore.
- Placér I/O i et repository; Bloc/Cubit orkestrerer kun og udsender tilstande.
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
}Hurtigt tjek
Et søgefelt skal sende en netværksanmodning, mens brugeren skriver, men først efter at brugeren har holdt pause i 300 ms. Samtidig skal enhver igangværende anmodning annulleres, når et nyere tastetryk kommer. Hvilket valg passer bedst, og hvorfor?
Opsummering
Du har lært, hvordan hver primitive omdanner input til tilstand, og hvordan du vælger:
- Cubit = metoder kalder
emitdirekte. Mindre standardkode; ideel til skift, tællere og simple formularer. - Bloc = begivenheder tilføjes med
addog knyttes til behandlere viaon<Event>. Du får begivenhedstransformere (debounce/throttle/droppable/restartable) og en sporbar Transition-log. - Modellér tilstande som sealed, uforanderlige klasser; brug
Equatabletil at deduplikere genopbygninger ogcopyWithtil at udlede næste tilstand. - Tommelfingerregel: Begynd med en Cubit, og opgradér kun til en Bloc, når du har brug for samtidighedskontrol eller et revisionsspor for begivenheder.
Lær Dart med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 22
- Lektioner
- 88
Ofte stillede spørgsmål
Er lektionen “Events, states og valget mellem Cubit og Bloc” gratis?
Ja — alle 3 lektioner i læringssporet Mobiludvikling med Flutter, inklusive “Events, states og valget mellem Cubit og Bloc”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Mobiludvikling med Flutter-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Events, states og valget mellem Cubit og Bloc”?
Vælg mellem Cubit og Bloc, og design rene transformationer fra events til states. Du øver dig i Mobiludvikling med Flutter med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på Mobiludvikling med Flutter?
Der kræves ingen tidligere erfaring. Mobiludvikling med Flutter på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 1 af 4.
Hvor lang tid tager lektionen “Events, states og valget mellem Cubit og Bloc”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne Mobiludvikling med Flutter-lektion?
Ja. Alle Mobiludvikling med Flutter-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Events, states og valget mellem Cubit og Bloc
- Stream-transformere og event-debouncing i BLoC
- Persistens af state med HydratedBloc
- Test af BLoCs med bloc_test og Mocktail