Mobilutvikling med Flutter · leksjon

Hendelser, tilstander og valget mellom Cubit og Bloc

Velg mellom Cubit og Bloc, og utform ryddige transformasjoner fra hendelse til tilstand.

Leksjon 1 av 413 trinn

Hendelser, tilstander og valget mellom Cubit og Bloc er en gratis leksjon i Mobilutvikling med Flutter på CoddyKit. Dette er leksjon 1 av 4. Du kan lese valgfritt 3 leksjoner fra denne læringsstien gratis i sin helhet – deretter låser CoddyKit PRO opp alle leksjoner, samt praktisk øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Mobilutvikling med Flutter, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Mobilutvikling med Flutter inneholder totalt 4 leksjoner.

To verktøy, én familie

Pakken flutter_bloc leveres med to primitiver for tilstandshåndtering: Cubit og Bloc. Begge utvider den samme basisklassen, og begge sender en strøm av tilstander til brukergrensesnittet.

  • Cubit eksponerer vanlige metoder som du kaller direkte, for eksempel increment().
  • Bloc reagerer på hendelser du legger til, for eksempel add(IncrementPressed()), og mapper dem til tilstander.

I denne leksjonen lærer du hvordan hver av dem omformer inndata til State, og hvordan du velger riktig løsning for en gitt funksjon.

Cubit: Fra metoder til tilstander

En Cubit er den enkleste primitiven. De utvider Cubit<T>, sender en starttilstand til super(...) og kaller emit(newState) i metoder for å sende en ny tilstand til lyttere.

Det finnes ikke noe hendelsesobjekt eller noe tilordningslag. Metoden er API-et.

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 hendelser til tilstander

En Bloc skiller hensikten (en hendelse) fra logikken (en behandler). De definerer hendelsesklasser og registrerer deretter behandlere med on<Event> i konstruktøren. UI-et kaller aldri logikken direkte; det legger bare til hendelser med add.

Dette indirekte laget krever mer standardkode, men gir én samlet og sporbar kanal for alle tilstandsendringer.

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));
  }
}

Modellere tilstander eksplisitt

En vanlig int fungerer for en teller, men virkelige funksjoner har flere former: lasting, vellykket og mislykket. Modellér disse som et forseglet klassehierarki, slik at kompilatoren tvinger UI-et til å håndtere alle tilfeller.

  • Forseglede klasser muliggjør uttømmende switch i Dart 3.
  • Hver tilstand inneholder bare dataene som er gyldige i den fasen.
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: Unngå unødvendige ombygginger

Bloc og Cubit varsler bare lyttere når den nye tilstanden ikke er lik den forrige. Som standard sammenligner Dart-objekter identitet, så to forskjellige instanser med samme data behandles som forskjellige og utløser en ombygging.

Overstyr likhet (vanligvis med pakken equatable) slik at tilstander med samme verdi dedupliseres og widgetene slutter å bygges om unødvendig.

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 arbeid i en behandler

De fleste virkelige behandlere er asynkrone: hent data, og send deretter ut en tilstand. Med en Bloc kan De sende ut flere tilstander fra én behandler — først en lastetilstand, deretter suksess eller feil.

Signaturen til behandleren gir Dem en emit-tilbakeringing i stedet for en returverdi, nettopp slik at De kan strømme flere tilstander under én hendelse.

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()));
      }
    });
  }
}

Hendelsestransformatorer: Bloc sin unike styrke

Dette er funksjonen som oftest avgjør valget. Med en Bloc styrer De hvordan samtidige hendelser behandles ved å sende en transformer til on<Event> (fra pakken bloc_concurrency):

  • concurrent() — håndterer alle hendelser parallelt (standard).
  • sequential() — én om gangen, i riktig rekkefølge.
  • droppable() — ignorerer nye hendelser mens én kjører (flott for knapper).
  • restartable() — avbryter behandleren som kjører når en nyere hendelse kommer (flott for søk).

Cubit har ingen hendelsesstrøm, så dette kan ikke gjøres uten å 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
  }
}

Observere overganger for feilsøking

Fordi en Bloc fører alle endringer gjennom hendelser, kan den rapportere en fullstendig Transition: gjeldende tilstand, den utløsende hendelsen og den neste tilstanden. Overstyr onTransition (eller bruk en global BlocObserver) for å logge denne kanalen.

En Cubit ser bare Change (gjeldende og neste tilstand) — det finnes ingen hendelse å logge, fordi det ikke finnes noen hendelse. Derfor foretrekkes Bloc for funksjoner som trenger et revisjonsspor.

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();
}

Tommelfingerregelen

En praktisk tommelfingerregel som Bloc-vedlikeholderne selv anbefaler: Start med en Cubit, og velg Bloc først når De trenger det en Bloc tilfører.

Velg Cubit når:

  • Logikken består av enkle, direkte metodekall (vekslere, tellere og skjemafelt).
  • De ikke trenger å debounce, throttle eller forkaste samtidige inndata.
  • De setter pris på mindre standardkode og enklere opplæring.

Velg Bloc når:

  • De trenger hendelsestransformatorer (debounce av søk eller forkasting av innsendinger).
  • De ønsker en sporbar hendelseslogg for analyse eller feilsøking.
  • Mange ulike inndata skal knyttes til én funksjon, og De vil dokumentere dem som hendelsestyper.

Samme funksjon, på begge måter

Sammenlign en veksler skrevet som en Cubit med en Bloc. Cubit-en er kortere og leses ovenfra og ned; Bloc legger til en hendelsestype og en behandler. For en ren veksler er Cubit det beste valget — Bloc-mekanismen gir Dem ingenting 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));
  }
}

Utforme ryddige transformasjoner fra hendelse til tilstand

Uansett hva De velger, bør transformasjonene være ryddige:

  • Tilstander er uforanderlige; bruk copyWith til å utlede neste tilstand i stedet for å endre den.
  • Én hendelse (eller metode) bør gi et sammenhengende sett med utsendte tilstander, aldri UI-navigasjon eller bivirkninger som De ikke kan spore.
  • Hold I/O i et repository; Bloc/Cubit skal bare koordinere og sende ut tilstander.
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
}

Hurtigsjekk

Et søkefelt må sende en nettverksforespørsel mens brukeren skriver, men først etter at brukeren har ventet i 300 ms, og avbryte alle pågående forespørsler når et nyere tastetrykk kommer. Hvilket valg passer best, og hvorfor?

Oppsummering

De har lært hvordan hver primitiv omdanner inndata til tilstand, og hvordan De velger:

  • Cubit = metoder kaller emit direkte. Mindre standardkode; ideelt for vekslere, tellere og enkle skjemaer.
  • Bloc = hendelser legges til med add og knyttes til behandlere via on<Event>. Dette gir hendelsestransformatorer (debounce/throttle/droppable/restartable) og en sporbar Transition-logg.
  • Modellér tilstander som forseglede, uforanderlige klasser; bruk Equatable for å deduplisere ombygginger og copyWith for å utlede neste tilstander.
  • Tommelfingerregel: Start med en Cubit, og oppgrader til en Bloc først når De trenger samtidighetskontroll eller et revisjonsspor for hendelser.
Gratis å komme i gang

Lær deg Dart med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
22
Leksjoner
88

Ofte stilte spørsmål

Er leksjonen «Hendelser, tilstander og valget mellom Cubit og Bloc» gratis?

Ja – du kan lese valgfritt 3 av leksjonene i læringsstien Mobilutvikling med Flutter, inkludert «Hendelser, tilstander og valget mellom Cubit og Bloc», gratis i sin helhet her på nettet. Deretter låser CoddyKit PRO opp alle leksjoner, samt interaktiv øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Kurset i Mobilutvikling med Flutter inneholder totalt 4 leksjoner.

Hva lærer jeg i «Hendelser, tilstander og valget mellom Cubit og Bloc»?

Velg mellom Cubit og Bloc, og utform ryddige transformasjoner fra hendelse til tilstand. Du øver på Mobilutvikling med Flutter med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med Mobilutvikling med Flutter?

Ingen tidligere erfaring er nødvendig. Mobilutvikling med Flutter på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 1 av 4.

Hvor lang tid tar leksjonen «Hendelser, tilstander og valget mellom Cubit og Bloc»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne Mobilutvikling med Flutter-leksjonen?

Ja. Alle Mobilutvikling med Flutter-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Hendelser, tilstander og valget mellom Cubit og Bloc
  2. Stream-transformatorer og debouncing av hendelser i BLoC
  3. Lagring av tilstand med HydratedBloc
  4. Testing av BLoC-er med bloc_test og Mocktail
← Tilbake til Mobilutvikling med Flutter