Mobilutvikling med Flutter · leksjon

Stream-transformatorer og debouncing av hendelser i BLoC

Bruk samtidighetstransformatorer til å begrense, debouncere og sekvensere innkommende hendelser.

Leksjon 2 av 413 trinn

Stream-transformatorer og debouncing av hendelser i BLoC er en gratis leksjon i Mobilutvikling med Flutter på CoddyKit. Dette er leksjon 2 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.

Hvorfor samtidighet for hendelser er viktig

I flutter_bloc sender hvert kall til add(event) en hendelse inn i en intern strøm. Som standard kjører hver hendelsesbehandler samtidig etter hvert som hendelsene kommer inn. Det er greit for de fleste hendelser, men enkelte kilder utløser hendelser altfor ofte:

  • Søkefelt sender en TextChanged-hendelse for hvert tastetrykk.
  • Rullelyttere sender dusinvis av ScrolledToBottom-hendelser per sekund.
  • Knapper kan trykkes raskt flere ganger og utløse dupliserte SubmitPressed-hendelser.

Å sende en nettverksforespørsel for hvert tastetrykk sløser med båndbredde og kan vise utdaterte resultater. I denne leksjonen lærer De hvordan De kan throttle, debounce og sekvensere disse hendelsene ved hjelp av strømtransformatorer i BLoC.

Parameteren transformer

Det moderne on<Event>-API-et godtar et valgfritt argument kalt transformer. En transformer er en EventTransformer<E> — en funksjon som mottar den innkommende Stream<E>-strømmen med hendelser og en mapper, og returnerer en transformert strøm.

Ved å angi en transformer styrer De hvordan hendelser av denne typen behandles: med debounce, throttle, forkasting eller én om gangen. Signaturen er:

  • typedef EventTransformer<Event> = Stream<Event> Function(Stream<Event> events, EventMapper<Event> mapper);

De skriver sjelden transformatorer selv — pakken bloc_concurrency inneholder de vanligste.

// Registering a handler with a custom transformer
on<SearchTermChanged>(
  _onSearchTermChanged,
  transformer: (events, mapper) => events
      .debounceTime(const Duration(milliseconds: 300))
      .switchMap(mapper),
);

Debounce: Vent på pausen

Debounce ignorerer hendelser til det har gått en periode uten nye hendelser. Hvis brukeren fortsatt skriver, nullstiller vi tidtakeren hver gang; først når brukeren for eksempel tar en pause på 300 ms, behandler vi den nyeste hendelsen.

Dette er riktig valg for et søkefelt: De vil spørre API-et når brukeren slutter å skrive, ikke ved hvert tastetrykk. Debounce forkaster alle mellomliggende hendelser og beholder bare den siste i hver serie.

  • Reduserer antallet nettverkskall betydelig.
  • Bytter litt ventetid (debounce-forsinkelsen) mot bedre effektivitet.

Debounce i ren Dart

Før vi kobler dette inn i BLoC, ser De her debounce-idéen uttrykt som et kjørbart Dart-program. Vi simulerer tastetrykk som kommer med varierende mellomrom, og skriver bare ut søkeordet når brukeren har tatt en pause på 300 ms.

RxDart-operatoren gjør dette for Dem, men ved å se tidtakerlogikken blir det tydeligere hva debounce betyr.

import 'dart:async';

void main() async {
  Timer? debounce;
  String? pending;
  final done = Completer<void>();

  void onChanged(String term) {
    pending = term;
    debounce?.cancel();
    debounce = Timer(const Duration(milliseconds: 300), () {
      print('search: $pending');
      if (pending == 'flutter') done.complete();
    });
  }

  // Fast burst, then a pause, then more typing.
  onChanged('f');
  await Future.delayed(const Duration(milliseconds: 50));
  onChanged('fl');
  await Future.delayed(const Duration(milliseconds: 50));
  onChanged('flu');
  await Future.delayed(const Duration(milliseconds: 400)); // pause -> fires
  onChanged('flutter');
  await done.future;
}

Throttle: Én per tidsvindu

Throttle slipper gjennom den første hendelsen og ignorerer deretter flere hendelser i et fast tidsvindu. I motsetning til debounce venter throttle ikke på en pause — den sender ut hendelsen umiddelbart og begrenser deretter frekvensen.

Dette passer for uendelig rulling og raske knappetrykk: De vil reagere på den første ScrolledToBottom med én gang, men ignorere stormen av duplikater som følger under samme rullebevegelse.

  • throttleTime med trailing: false = bare fremre kant (reager nå, og kjøl deretter ned).
  • Forhindrer duplikate sidelastinger eller doble innsendinger.
// Infinite-scroll feed: react immediately, then cool down 500ms
on<FeedScrolledToEnd>(
  _onScrolledToEnd,
  transformer: (events, mapper) => events
      .throttleTime(const Duration(milliseconds: 500))
      .asyncExpand(mapper),
);

bloc_concurrency-transformatorer

Den offisielle pakken bloc_concurrency leveres med fire ferdige transformatorer som styrer hvordan overlappende hendelser håndteres:

  • concurrent() — behandlere kjører parallelt (standard).
  • sequential() — hendelser legges i kø og behandles strengt én etter én.
  • droppable() — mens en behandler kjører, forkastes nye hendelser av denne typen.
  • restartable() — en ny hendelse avbryter behandleren som kjører, og starter på nytt.

Disse kan kombineres med tidsoperatorer: for eksempel debounce først, og deretter restartable() for å avbryte et utdatert søk.

import 'package:bloc_concurrency/bloc_concurrency.dart';

// Submit button: ignore extra taps while the first submit is in flight
on<FormSubmitted>(_onSubmit, transformer: droppable());

// Saving steps that must run in order
on<StepSaved>(_onStepSaved, transformer: sequential());

Kombinere debounce med restartable

For en søke-BLoC er den ideelle oppskriften debounce, deretter restartable:

  • Bruk debounce på TextChanged-hendelsene, slik at De bare spør etter data når brukeren tar en pause.
  • Bruk restartable() slik at et nyere søk avbryter den gamle forespørselen hvis den fortsatt lastes, slik at det gamle resultatet aldri overskriver nye resultater.

Sammen fjerner de både unødvendige kall og svar som kommer i feil rekkefølge. De pakker transformatoren fra bloc_concurrency inn i en liten hjelpefunksjon som bruker debounceTime først.

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

EventTransformer<E> debounceRestartable<E>(Duration duration) {
  return (events, mapper) =>
      restartable<E>().call(events.debounceTime(duration), mapper);
}

// Usage inside a Bloc constructor:
on<SearchTermChanged>(
  _onSearchTermChanged,
  transformer: debounceRestartable(const Duration(milliseconds: 300)),
);

En komplett søke-Bloc

Slik henger delene sammen i en virkelig SearchBloc. Legg merke til at behandleren er async og kan emit flere tilstander (lasting, deretter resultater eller feil). Fordi transformatoren er restartable, slutter en utdatert forespørsel å sende ut tilstander så snart et nyere søkeord kommer inn.

class SearchBloc extends Bloc<SearchEvent, SearchState> {
  final SearchRepository repo;

  SearchBloc(this.repo) : super(const SearchState.initial()) {
    on<SearchTermChanged>(
      _onTermChanged,
      transformer: debounceRestartable(const Duration(milliseconds: 300)),
    );
  }

  Future<void> _onTermChanged(
    SearchTermChanged event,
    Emitter<SearchState> emit,
  ) async {
    final term = event.term.trim();
    if (term.isEmpty) {
      emit(const SearchState.initial());
      return;
    }
    emit(const SearchState.loading());
    try {
      final results = await repo.search(term);
      emit(SearchState.success(results));
    } catch (e) {
      emit(SearchState.failure(e.toString()));
    }
  }
}

Tilordningsoperatorer: switchMap kontra asyncExpand

Inne i en egendefinert transformer bestemmer De hvordan mapperen brukes på hver hendelse:

  • switchMap(mapper) — avbryter den forrige indre strømmen når en ny hendelse kommer. Tilsvarer i prinsippet restartable.
  • exhaustMap(mapper) — ignorerer nye hendelser mens én er aktiv. Tilsvarer droppable.
  • asyncExpand(mapper) — kjører sekvensielt; hver hendelse venter til den forrige behandleren er ferdig. Tilsvarer sequential.
  • flatMap(mapper) — kjører alle samtidig. Tilsvarer concurrent.

Foretrekk de navngitte bloc_concurrency-transformatorene for tydelighetens skyld; bruk rå RxDart bare når De må kombinere tidsstyring og tilordning i ett uttrykk.

Throttle-sekvensdemonstrasjon i Dart

Dette kjørbare eksempelet modellerer throttle uten noe rammeverk. Den første hendelsen i hvert tidsvindu på 200 ms behandles; hendelser som kommer under nedkjølingsperioden, forkastes. Se hvordan bare de fremste hendelsene slipper gjennom.

import 'dart:async';

void main() async {
  DateTime? lastAccepted;
  const window = Duration(milliseconds: 200);
  final accepted = <int>[];

  void onEvent(int id) {
    final now = DateTime.now();
    if (lastAccepted == null || now.difference(lastAccepted!) >= window) {
      lastAccepted = now;
      accepted.add(id);
    }
  }

  // Fire 6 events; some land inside the cooldown window.
  for (var i = 1; i <= 6; i++) {
    onEvent(i);
    await Future.delayed(const Duration(milliseconds: 90));
  }

  // Only leading-edge events per 200ms window are kept.
  print('accepted: $accepted');
}

Velge riktig strategi

Velg transformer ut fra brukerens intensjon:

  • Søk / autofullføring: debounce + restartable — vent på pausen og avbryt foreldede forespørsler.
  • Lasting av sider ved uendelig rulling: throttle + droppable — last inn én gang og ignorer resten av bevegelsen.
  • Sending av skjema / betaling: droppable — blokker duplikate innsendinger mens én allerede behandles.
  • Sekvensielle skrivinger (lagringstrinn, analyse): sequential — bevar rekkefølgen uten overlapping.

Legg alltid til bloc_concurrency og rxdart i pubspec.yaml, og husk at en transformer bare påvirker den ene hendelsestypen den er registrert for.

Hurtigsjekk

Test forståelsen Deres av scenarioet med søkefeltet.

Oppsummering

De har lært hvordan De kan styre samtidighet for hendelser i BLoC med stream-transformere:

  • Håndtereren on<Event> tar imot en transformer som former den innkommende hendelsesstrømmen på nytt.
  • Debounce venter på en pause (svært nyttig for søk); throttle reagerer på den første hendelsen og tar deretter en pause (svært nyttig ved rulling og raske trykk).
  • bloc_concurrency tilbyr concurrent, sequential, droppable og restartable, som tilsvarer RxDart-metodene flatMap, asyncExpand, exhaustMap og switchMap.
  • Den anbefalte oppskriften for søk er debounce then restartable; innsendinger bruker droppable; sekvensielle skrivinger bruker sequential.

Velg transformeren som samsvarer med brukerens intensjon, og husk at den bare gjelder hendelsestypen den er registrert for.

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 «Stream-transformatorer og debouncing av hendelser i BLoC» gratis?

Ja – du kan lese valgfritt 3 av leksjonene i læringsstien Mobilutvikling med Flutter, inkludert «Stream-transformatorer og debouncing av hendelser i 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 «Stream-transformatorer og debouncing av hendelser i BLoC»?

Bruk samtidighetstransformatorer til å begrense, debouncere og sekvensere innkommende hendelser. 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 2 av 4.

Hvor lang tid tar leksjonen «Stream-transformatorer og debouncing av hendelser i 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