Stream-muuntimet ja tapahtumien debouncing BLoCissa
Käytä samanaikaisuuden muuntimia saapuvien tapahtumien rajoittamiseen, debouncingiin ja järjestämiseen.
Stream-muuntimet ja tapahtumien debouncing BLoCissa on ilmainen Mobiilikehitys Flutterilla-oppitunti CoddyKitissä. Tämä on oppitunti 2/4. Voit lukea tästä oppimispolusta kokonaan mitkä tahansa 3 oppituntia ilmaiseksi — sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä käytännön harjoittelun sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Oppitunti kuuluu Mobiilikehitys Flutterilla-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Mobiilikehitys Flutterilla-kurssilla on yhteensä 4 oppituntia.
Miksi tapahtumien samanaikaisuudella on merkitystä
flutter_bloc-kirjastossa jokainen add(event)-kutsu lisää tapahtuman sisäiseen virtaan. Oletusarvoisesti kunkin tapahtuman käsittelijä suoritetaan samanaikaisesti tapahtumien saapuessa. Useimpien tapahtumien kohdalla tämä on sopivaa, mutta jotkin lähteet tuottavat tapahtumia aivan liian usein:
- Hakupalkit tuottavat
TextChanged-tapahtuman jokaisella näppäinpainalluksella. - Vierityskuuntelijat tuottavat kymmeniä
ScrolledToBottom-tapahtumia sekunnissa. - Painikkeita voidaan napauttaa nopeasti, jolloin syntyy toistuvia
SubmitPressed-tapahtumia.
Verkkopyynnön tekeminen jokaisella näppäinpainalluksella tuhlaa kaistanleveyttä ja voi näyttää vanhentuneita tuloksia. Tässä oppitunnissa opitte rajoittamaan nopeutta, odottamaan syötteen hiljentymistä ja käsittelemään tapahtumat järjestyksessä BLoCin stream-muuntimilla.
transformer-parametri
Moderni on<Event>-rajapinta hyväksyy valinnaisen transformer-argumentin. Muunnin on EventTransformer<E> — funktio, joka vastaanottaa saapuvien tapahtumien Stream<E>-virran ja mapperin ja palauttaa muunnetun virran.
Muuntimen avulla hallitsette, miten kyseisen tyypin tapahtumat käsitellään: viivästettyinä, nopeusrajoitettuina, hylättyinä tai yksi kerrallaan. Allekirjoitus on:
typedef EventTransformer<Event> = Stream<Event> Function(Stream<Event> events, EventMapper<Event> mapper);
Muuntimia kirjoitetaan harvoin itse — bloc_concurrency-paketti tarjoaa yleisimmät valmiina.
// Registering a handler with a custom transformer
on<SearchTermChanged>(
_onSearchTermChanged,
transformer: (events, mapper) => events
.debounceTime(const Duration(milliseconds: 300))
.switchMap(mapper),
);Debounce: odottakaa taukoa
Debounce-käsittely jättää tapahtumat huomiotta, kunnes hiljainen ajanjakso on kulunut. Jos käyttäjä kirjoittaa edelleen, ajastin käynnistetään aina uudelleen; vasta kun käyttäjä on esimerkiksi 300 ms tauolla, käsittelemme uusimman tapahtuman.
Tämä sopii hakukenttään: haluatte kysellä rajapinnasta vasta, kun käyttäjä lopettaa kirjoittamisen, ettekä jokaisella näppäinpainalluksella. Debounce hylkää kaikki tapahtumaryöpyn välissä olevat tapahtumat ja säilyttää vain viimeisen.
- Vähentää verkkokutsujen määrää huomattavasti.
- Tehokkuuden saavuttamiseksi hyväksyy hieman viivettä (debounce-viiveen).
Debounce tavallisella Dartilla
Ennen kuin yhdistämme sen BLoCiin, tarkastelemme debounce-ideaa suoritettavana Dart-ohjelmana. Simuloimme näppäinpainalluksia, joiden väliset tauot vaihtelevat, ja tulostamme termin vasta, kun kirjoittamiseen tulee 300 ms:n tauko.
RxDart-operaattori hoitaa tämän puolestanne, mutta ajastinlogiikan näkeminen selventää, mitä debounce tarkoittaa.
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: yksi tapahtuma aikavälissä
Throttle-käsittely päästää ensimmäisen tapahtuman läpi ja jättää sitten uudet tapahtumat huomiotta kiinteän aikavälin ajan. Toisin kuin debounce, throttle ei odota taukoa — se tuottaa tapahtuman heti ja rajoittaa sen jälkeen tapahtumatiheyttä.
Tämä sopii loputtomaan vieritykseen ja nopeisiin painikkeen napautuksiin: haluatte reagoida ensimmäiseen ScrolledToBottom-tapahtumaan heti, mutta jättää saman vierityseleen aikana seuraavan tapahtumatulvan huomiotta.
throttleTimeyhdessätrailing: false-asetuksen kanssa = vain etureuna (toimi nyt ja jäähdyttele sitten).- Estää sivujen lataamisen tai lomakkeen lähettämisen kahdesti.
// 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-muuntimet
Virallinen bloc_concurrency-paketti sisältää neljä valmista muunninta, jotka hallitsevat päällekkäisten tapahtumien käsittelyä:
concurrent()— käsittelijät suoritetaan rinnakkain (oletus).sequential()— tapahtumat jonotetaan ja käsitellään täsmälleen yksi kerrallaan.droppable()— käsittelijän ollessa käynnissä uudet saman tyypin tapahtumat hylätään.restartable()— uusi tapahtuma peruuttaa käynnissä olevan käsittelijän ja aloittaa alusta.
Nämä voidaan yhdistää ajoitusoperaattoreihin: käyttäkää esimerkiksi ensin debouncea ja sitten restartable()-muunninta vanhentuneen haun peruuttamiseen.
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());Debouncen ja restartablen yhdistäminen
Hakua käsittelevälle BLoCille ihanteellinen ratkaisu on debounce ja sitten restartable:
- Debounce-käsitelkää
TextChanged-tapahtumat, jotta teette kyselyn vasta käyttäjän pidettyä tauon. - Käyttäkää restartable()-muunninta, jotta uusi kysely peruuttaa aiemman pyynnön sen ollessa vielä latauksessa eikä vanhentunut pyyntö koskaan korvaa tuoreita tuloksia.
Yhdessä nämä poistavat sekä turhat kutsut että väärässä järjestyksessä saapuvien vastausten ongelman. Kääräisette bloc_concurrency-paketin muuntimen pieneen apufunktioon, joka käyttää ensin debounceTime-operaattoria.
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)),
);Täydellinen hakua käsittelevä Bloc
Näin osat yhdistyvät oikeassa SearchBloc-toteutuksessa. Huomatkaa, että käsittelijä on async ja voi välittää emit-kutsulla useita tiloja (latauksen ja sitten tulokset tai virheen). Koska muunnin on restartable, vanhentunut pyyntö lakkaa välittämästä tiloja heti, kun uudempi hakutermi saapuu.
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()));
}
}
}Muunnosoperaattorit: switchMap ja asyncExpand
Mukautetussa muuntimessa päätätte, miten mapperia sovelletaan kuhunkin tapahtumaan:
switchMap(mapper)— peruuttaa edellisen sisäisen virran, kun uusi tapahtuma saapuu. Vastaa periaatteessarestartable-muunninta.exhaustMap(mapper)— jättää uudet tapahtumat huomiotta, kun yksi on aktiivinen. Vastaadroppable-muunninta.asyncExpand(mapper)— suorittaa tapahtumat peräkkäin; kukin tapahtuma odottaa edellisen käsittelijän valmistumista. Vastaasequential-muunninta.flatMap(mapper)— suorittaa kaikki tapahtumat samanaikaisesti. Vastaaconcurrent-muunninta.
Suosikaa nimettyjä bloc_concurrency-muuntimia selkeyden vuoksi; käyttäkää suoraa RxDartia vain, kun ajoitus ja muunnos on yhdistettävä samaan lausekkeeseen.
Throttle-järjestysesimerkki Dartilla
Tämä suoritettava esimerkki mallintaa throttle-käsittelyä ilman mitään frameworkia. Kunkin 200 ms:n aikavälin ensimmäinen tapahtuma käsitellään; jäähdyttelyn aikana saapuvat tapahtumat hylätään. Seuratkaa, kuinka vain etummaiset tapahtumat jäävät jäljelle.
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');
}Oikean strategian valitseminen
Yhdistä muunnin käyttäjän tarkoitukseen:
- Haku / automaattinen täydennys: debounce + restartable — odota taukoa ja peru vanhentuneet kyselyt.
- Äärettömän vierityksen sivulataus: throttle + droppable — lataa kerran ja ohita loput eleen aikana.
- Lomakkeen lähetys / maksu: droppable — estä päällekkäiset lähetykset yhden ollessa käynnissä.
- Järjestyksessä tehtävät kirjoitukset (vaiheiden tallennus, analytiikka): sequential — säilytä järjestys äläkä salli päällekkäisyyksiä.
Lisää aina bloc_concurrency ja rxdart tiedostoon pubspec.yaml ja muista, että muunnin vaikuttaa vain siihen tapahtumatyyppiin, johon se on rekisteröity.
Pikatarkistus
Testaa ymmärryksesi hakukenttää koskevasta tilanteesta.
Kertaus
Opit hallitsemaan tapahtumien samanaikaisuutta BLoCissa stream-muuntimien avulla:
on<Event>-käsittelijä vastaanottaatransformer-muuntimen, joka muokkaa saapuvaa tapahtumavirtaa.- Debounce odottaa taukoa (sopii erinomaisesti hakuun); throttle käsittelee tapahtuman ensin ja pitää sitten tauon (sopii vieritykseen ja nopeisiin napautuksiin).
bloc_concurrencytarjoaa muuntimetconcurrent,sequential,droppablejarestartable, jotka vastaavat RxDartin muuntimiaflatMap,asyncExpand,exhaustMapjaswitchMap.- Hakujen vakiintunut ratkaisu on debounce ja sitten restartable; lähetyksissä käytetään droppable-muunninta ja järjestyksessä tehtävissä kirjoituksissa sequential-muunninta.
Valitse käyttäjän tarkoitukseen sopiva muunnin ja muista, että se vaikuttaa vain siihen tapahtumatyyppiin, johon se on rekisteröity.
Opi Dart tekoälytuutorin avulla — ilmaiseksi
Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.
- Kurssit
- 22
- Oppitunnit
- 88
Usein kysytyt kysymykset
Onko oppitunti ”Stream-muuntimet ja tapahtumien debouncing BLoCissa” ilmainen?
Kyllä — voit lukea täällä verkossa kokonaan ilmaiseksi mitkä tahansa Mobiilikehitys Flutterilla-oppimispolun 3 oppituntia, myös oppitunnin “Stream-muuntimet ja tapahtumien debouncing BLoCissa”. Sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä interaktiiviset harjoitukset sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Mobiilikehitys Flutterilla-kurssilla on yhteensä 4 oppituntia.
Mitä opin oppitunnilla ”Stream-muuntimet ja tapahtumien debouncing BLoCissa”?
Käytä samanaikaisuuden muuntimia saapuvien tapahtumien rajoittamiseen, debouncingiin ja järjestämiseen. Harjoittelet Mobiilikehitys Flutterilla-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.
Tarvitsenko kokemusta aloittaakseni Mobiilikehitys Flutterilla-opiskelun?
Aiempi kokemus ei ole tarpeen. CoddyKitin Mobiilikehitys Flutterilla-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 2/4.
Kuinka kauan ”Stream-muuntimet ja tapahtumien debouncing BLoCissa”-oppitunnin suorittaminen kestää?
Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.
Voinko kirjoittaa ja suorittaa koodia tällä Mobiilikehitys Flutterilla-oppitunnilla?
Kyllä. Jokainen Mobiilikehitys Flutterilla-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.
Kaikki tämän kurssin oppitunnit
- Tapahtumat, tilat ja Cubit-vs-Bloc-valinta
- Stream-muuntimet ja tapahtumien debouncing BLoCissa
- Tilan säilyttäminen HydratedBlocilla
- BLoCien testaaminen bloc_testillä ja Mocktaililla