Tapahtumat, tilat ja Cubit-vs-Bloc-valinta
Valitse Cubitin ja Blocin välillä ja suunnittele selkeät muunnokset tapahtumista tiloiksi.
Tapahtumat, tilat ja Cubit-vs-Bloc-valinta on ilmainen Mobiilikehitys Flutterilla-oppitunti CoddyKitissä. Tämä on oppitunti 1/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.
Kaksi työkalua, yksi perhe
flutter_bloc-paketti tarjoaa kaksi tilanhallinnan peruskomponenttia: Cubitin ja Blocin. Molemmat periytyvät samasta kantaluokasta ja lähettävät käyttöliittymälle tiloja sisältävän virran.
- Cubit tarjoaa tavallisia metodeja, joita kutsutaan suoraan, esimerkiksi
increment(). - Bloc reagoi lisättäviin tapahtumiin, esimerkiksi
add(IncrementPressed()), ja muuntaa ne tiloiksi.
Tässä oppitunnissa opit, miten kumpikin muuntaa syötteen State-tilaksi ja miten valita oikea vaihtoehto tiettyä ominaisuutta varten.
Cubit: metodeista tiloiksi
Cubit on yksinkertaisempi primitiivi. Laajennatte Cubit<T>-luokkaa, välitätte alkutilan super(...)-kutsulle ja kutsutte metodeissa emit(newState)-metodia uuden tilan välittämiseksi kuuntelijoille.
Erillistä tapahtumaoliota tai muunnoskerrosta ei ole. Metodi on rajapinnan näkyvä osa.
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: tapahtumista tiloiksi
Bloc erottaa toisistaan tarkoituksen (tapahtuman) ja logiikan (käsittelijän). Määrittelette tapahtumaluokat ja rekisteröitte sitten käsittelijät konstruktorissa on<Event>-kutsulla. Käyttöliittymä ei kutsu logiikkaa suoraan, vaan ainoastaan lisää tapahtumia add-kutsulla.
Tämä epäsuoruus lisää hieman mallikoodia, mutta tarjoaa yhden jäljitettävän väylän kaikille tilan muutoksille.
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));
}
}Tilojen eksplisiittinen mallintaminen
Tavallinen int riittää laskuriin, mutta oikeissa ominaisuuksissa on useita muotoja: lataus, onnistuminen ja virhe. Mallintakaa nämä sealed-luokkahierarkiana, jotta kääntäjä pakottaa käyttöliittymän käsittelemään kaikki tapaukset.
- Sealed-luokat mahdollistavat kattavan
switch-lauseen Dart 3:ssa. - Kukin tila sisältää vain kyseisessä vaiheessa kelvolliset tiedot.
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: turhien uudelleenrakennusten välttäminen
Bloc ja Cubit ilmoittavat kuuntelijoille vain, kun uusi tila ei ole sama kuin edellinen tila. Oletusarvoisesti Dart-oliot vertaillaan identiteetin perusteella, joten kahta eri instanssia, joilla on samat tiedot, pidetään eri olioina ja ne käynnistävät uudelleenrakennuksen.
Ohittakaa yhtäsuuruusvertailu (yleensä equatable-paketin avulla), jotta samanarvoiset tilat voidaan yhdistää ja widgetit lakkaavat rakentumasta uudelleen tarpeettomasti.
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.Asynkroninen työ käsittelijän sisällä
Useimmat todellisten sovellusten käsittelijät ovat asynkronisia: noutakaa tiedot ja välittäkää sitten tila. Blocissa voitte välittää tilan useita kertoja yhdestä käsittelijästä — ensin lataustilan ja sitten onnistumis- tai virhetilan.
Käsittelijän allekirjoitus antaa käyttöön emit-takaisinkutsun paluuarvon sijaan juuri siksi, että voitte välittää useita tiloja yhden tapahtuman aikana.
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()));
}
});
}
}Tapahtumamuuntimet: vain Blocin supervoima
Tämä ominaisuus ratkaisee useimmiten, kumpi vaihtoehto kannattaa valita. Blocissa voitte hallita samanaikaisten tapahtumien käsittelytapaa välittämällä transformer-muuntimen on<Event>-kutsulle (bloc_concurrency-paketista):
concurrent()— käsittelee kaikki tapahtumat rinnakkain (oletus).sequential()— käsittelee tapahtumat yksi kerrallaan järjestyksessä.droppable()— jättää uudet tapahtumat huomiotta, kun yksi on käynnissä (sopii hyvin painikkeille).restartable()— peruuttaa käynnissä olevan käsittelijän, kun uudempi tapahtuma saapuu (sopii hyvin hakuun).
Cubitilla ei ole tapahtumavirtaa, joten tämä ei onnistu ilman, että kirjoitatte debounce- tai throttle-logiikan itse.
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
}
}Siirtymien tarkkailu virheenkorjausta varten
Koska Bloc välittää jokaisen muutoksen tapahtumien kautta, se voi raportoida täydellisen Transition-siirtymän: nykyisen tilan, muutoksen käynnistäneen tapahtuman ja seuraavan tilan. Ohittakaa onTransition (tai käyttäkää globaalia BlocObserver-tarkkailijaa) tämän tapahtumaväylän kirjaamiseen.
Cubit näkee vain Change-muutoksen (nykyisen ja seuraavan tilan) — kirjattavaa tapahtumaa ei ole, koska tapahtumaa ei ole. Siksi Blocia suositaan ominaisuuksissa, jotka tarvitsevat tarkastusketjun.
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();
}Valintasääntö
Käytännöllinen nyrkkisääntö, jota Blocin ylläpitäjät itse suosittelevat: aloittakaa Cubitilla ja valitkaa Bloc vasta, kun tarvitsette jotain, minkä Bloc tuo lisää.
Valitkaa Cubit, kun:
- Logiikka koostuu yksinkertaisista suorista metodikutsuista (vaihtimet, laskurit ja lomakekentät).
- Teidän ei tarvitse tehdä samanaikaisten syötteiden debounce-, throttle- tai droppable-käsittelyä.
- Arvostatte vähäisempää mallikoodia ja helpompaa perehdytystä.
Valitkaa Bloc, kun:
- Tarvitsette tapahtumamuuntimia (haun debounce-käsittelyä tai lähetysten droppable-käsittelyä).
- Haluatte jäljitettävän tapahtumalokin analytiikkaa tai virheenkorjausta varten.
- Yhteen ominaisuuteen liittyy monia erilaisia syötteitä ja haluatte dokumentoida ne tapahtumatyyppeinä.
Sama ominaisuus kahdella tavalla
Verratkaa Cubitilla ja Blocilla kirjoitettua vaihtimen toteutusta. Cubit on lyhyempi ja etenee ylhäältä alas; Bloc lisää tapahtumatyypin ja käsittelijän. Puhtaassa vaihtimessa Cubit on parempi valinta — Blocin rakenteesta ei ole tässä hyötyä.
// 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));
}
}Selkeiden tapahtumasta tilaksi -muunnosten suunnittelu
Kumman tahansa valitsettekin, pitäkää muunnokset selkeinä:
- Tilat ovat muuttumattomia; käyttäkää
copyWith-metodia seuraavan tilan muodostamiseen muuttamisen sijaan. - Yhden tapahtuman (tai metodin) tulee vastata yhtenäistä välitettyjen tilojen joukkoa, ei koskaan käyttöliittymän navigointia tai sivuvaikutuksia, joita ei voi jäljittää.
- Pidättehän I/O-toiminnot repositoriossa; Bloc/Cubit ainoastaan ohjaa toimintaa ja välittää tiloja.
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
}Pikatarkistus
Hakukentän on tehtävä verkkopyyntö käyttäjän kirjoittaessa, mutta vasta, kun käyttäjä on ollut 300 ms kirjoittamatta, ja peruutettava käynnissä oleva pyyntö, kun uusi näppäinpainallus saapuu. Mikä vaihtoehto sopii parhaiten ja miksi?
Kertaus
Opitte, miten kumpikin primitiivi muuntaa syötteen tilaksi ja miten valinta tehdään:
- Cubit = metodit kutsuvat
emit-metodia suoraan. Vähemmän mallikoodia; sopii ihanteellisesti vaihtimille, laskureille ja yksinkertaisille lomakkeille. - Bloc = tapahtumat lisätään
add-kutsulla ja muunnetaanon<Event>-kutsun avulla. Vastineeksi saatte tapahtumamuuntimet (debounce/throttle/droppable/restartable) ja jäljitettävän Transition-lokin. - Mallintakaa tilat sealed- ja immutable-luokkina; käyttäkää
Equatable-luokkaa uudelleenrakennusten yhdistämiseen jacopyWith-metodia seuraavien tilojen muodostamiseen. - Nyrkkisääntö: aloittakaa Cubitilla ja siirtykää Blociin vain, kun tarvitsette samanaikaisuuden hallintaa tai tapahtumien tarkastusketjun.
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 ”Tapahtumat, tilat ja Cubit-vs-Bloc-valinta” ilmainen?
Kyllä — voit lukea täällä verkossa kokonaan ilmaiseksi mitkä tahansa Mobiilikehitys Flutterilla-oppimispolun 3 oppituntia, myös oppitunnin “Tapahtumat, tilat ja Cubit-vs-Bloc-valinta”. 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 ”Tapahtumat, tilat ja Cubit-vs-Bloc-valinta”?
Valitse Cubitin ja Blocin välillä ja suunnittele selkeät muunnokset tapahtumista tiloiksi. 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 1/4.
Kuinka kauan ”Tapahtumat, tilat ja Cubit-vs-Bloc-valinta”-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