26 β Bloc va Cubit¶
β¬ οΈ Oldingi: 25 β Riverpod (3.x) Β· π README Β· Keyingi: 27 β Animatsiya β‘οΈ
Bu bobda: holatni boshqarishning yana bir mashhur β ayniqsa katta jamoalar va korxona (enterprise) loyihalarida sevimli β yondashuvi Bloc bilan tanishasiz. Avval uning soddaroq yarmi Cubit ni (
emitbilan to'g'ridan-to'g'ri holat chiqarish), so'ng to'la Bloc ni (Event → ishlovchi → State) o'rganamiz. Holatni sealed klass bilan modellashtirishni (8-bobdagi naqsh),BlocBuilder/BlocListener/BlocConsumerbilan UI ni ulashni va asinxron "foydalanuvchilarni yuklash" oqimini quramiz. Oxirida Provider, Riverpod va Bloc ni taqqoslab, qaysi birini qachon tanlashni hal qilamiz.
Nega aynan Bloc?¶
24-bobda Provider, 25-bobda Riverpod bilan holatni (state) boshqarishni ko'rdingiz. Ularning ikkalasi ham ajoyib. Unda nega yana bir kutubxona kerak?
Tasavvur qiling: katta jamoa, o'nlab ekran, murakkab biznes-mantiq. Bunday loyihada eng og'riqli savol β "holat nega o'zgardi?". Tugma bosildimi? Tarmoqdan javob keldimi? Qaysi kod bu o'zgarishni keltirib chiqardi? Provider'da har kim notifyListeners() ni xohlagan joyda chaqirishi mumkin β izini topish qiyinlashadi.
Bloc boshqacha qoida qo'yadi: har bir o'zgarish oshkora va bir yo'nalishli bo'lsin. G'oya juda sodda:
- UI hodisa (Event) chiqaradi β "foydalanuvchi tugmani bosdi".
- Bloc bu hodisani qabul qiladi, biznes-mantiqni bajaradi.
- Bloc yangi holat (State) ni chiqaradi (emit).
- UI yangi holatga qarab qayta quriladi.
Ma'lumot doimo bir tomonga oqadi: UI → Event → Bloc → State → UI. Bunga bir yo'nalishli oqim (unidirectional data flow) deyiladi. Buning foydasi katta:
- Bashoratli (predictable): holat faqat
emitorqali o'zgaradi, boshqa hech qanday "yashirin" yo'l yo'q. - Kuzatiladigan (traceable): har bir Event va har bir State β alohida obyekt. Ularni jurnalga yozish, kuzatish oson.
- Testlanadigan: "shu Event kelsa, shu State chiqishi kerak" β toza, sof funksiya kabi sinaladi.
Bloc poydevorida stream'lar (oqimlar) yotadi β asinxron bobda ko'rganimizdek, oqim β vaqt o'tib kelayotgan qiymatlar ketma-ketligi. Bloc ham aslida "holatlar oqimi"ni chiqarib turadi; siz uni faqat o'rab olingan, qulay API orqali ishlatasiz, oqim mexanikasi bilan ovora bo'lmaysiz.
Eslatma: "Bloc" so'zi ikki ma'noda ishlatiladi β umumiy naqsh/kutubxona nomi sifatida ("Bloc bilan yozish") va aniq
Blocklassi sifatida (Cubitga qarama-qarshi). Kontekstdan farqlanadi; biz aniq klassni nazarda tutganda har doim koddagidekBlocdeb yozamiz.
O'rnatish¶
Loyihaga ikki paketni qo'shamiz β bloc (sof Dart yadrosi) va flutter_bloc (Flutter vidjetlari):
Terminalda:
Keyinroq bloc_test, hydrated_bloc haqida ham qisqacha gaplashamiz.
1. Cubit β soddaroq yarmidan boshlaymiz¶
Bloc'ni o'rganishni Cubit dan boshlash to'g'ri, chunki u β Bloc'ning soddalashtirilgan ko'rinishi. Cubit'da hodisa (event) yo'q. Siz shunchaki metodlar yozasiz, ular ichida emit bilan yangi holat chiqarasiz. Tamom.
Klassik misol β sanagich (counter). Holat β bitta int (joriy son):
import 'package:flutter_bloc/flutter_bloc.dart';
// Cubit<int> β holatimiz tipi int
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0); // boshlang'ich holat: 0
void increment() => emit(state + 1); // joriy holatga (state) 1 qo'shamiz
void decrement() => emit(state - 1);
}
Bu yerda nimalar bo'lyapti?
Cubit<int>β burchakli qavs ichidagiintbizning holat tipimiz. Holat istalgan tip bo'lishi mumkin: son,String, klass, sealed klass.super(0)β boshlang'ich holat. Cubit yaratilganda holat darrov0bo'ladi.stateβ har qanday joyda joriy holatni o'qiydigan xossa. Uni siz qo'lda o'zgartira olmaysiz.emit(...)β yangi holatni chiqaradigan yagona yo'l.emitchaqirilgach, bu Cubit'ni tinglovchi barcha UI yangilanadi.
Diqqat: holatni faqat emit o'zgartiradi. state = ... deb yozolmaysiz β bu xato. Aynan shu cheklov holatni bashoratli qiladi.
2. Cubit ni UI ga ulash¶
Cubit yozildi β endi uni ekranga bog'laymiz. Uch qadam bor: berish (provide), o'qish (read) va qayta qurish (build).
BlocProvider β Cubit ni berish¶
Vidjet daraxtining yuqorisida BlocProvider joylashtiramiz. U Cubit'ni yaratadi va undan pastdagi barcha vidjetlarga yetkazadi:
BlocProvider(
create: (_) => CounterCubit(), // Cubit shu yerda yaratiladi
child: const CounterPage(),
)
Bir nechta Cubit/Bloc kerak bo'lsa, ularni MultiBlocProvider ichiga yig'asiz (xuddi Provider'dagi MultiProvider kabi):
MultiBlocProvider(
providers: [
BlocProvider(create: (_) => CounterCubit()),
BlocProvider(create: (_) => AuthCubit()),
],
child: const MyApp(),
)
context.read β metod chaqirish¶
Tugma bosilganda Cubit metodini chaqirish kerak. Buni context.read<CounterCubit>() orqali qilamiz β u Cubit obyektining o'zini topib beradi, lekin qayta qurishga obuna bo'lmaydi (shuning uchun tugma ishlovchilarida xuddi shuni ishlatamiz):
BlocBuilder β holatga qarab qayta qurish¶
Sonni ekranga chiqarish va u o'zgarganda yangilanish uchun BlocBuilder ni ishlatamiz. U Cubit'ni tinglaydi va har safar yangi holat kelganda builder ni qayta chaqiradi:
BlocBuilder<CounterCubit, int>(
builder: (context, state) {
return Text('$state', style: const TextStyle(fontSize: 48));
},
)
Burchakli qavslar: birinchi β qaysi Cubit/Bloc (CounterCubit), ikkinchi β uning holat tipi (int). builder ga keladigan state β aynan joriy holat.
To'liq sahifa shunday ko'rinadi:
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Sanagich (Cubit)')),
body: Center(
child: BlocBuilder<CounterCubit, int>(
builder: (context, state) =>
Text('$state', style: const TextStyle(fontSize: 48)),
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => context.read<CounterCubit>().decrement(),
child: const Icon(Icons.remove),
),
const SizedBox(width: 12),
FloatingActionButton(
onPressed: () => context.read<CounterCubit>().increment(),
child: const Icon(Icons.add),
),
],
),
);
}
}
readvawatchfarqi:context.read<T>()faqat obyektni oladi, qayta qurishni keltirib chiqarmaydi β unionPressedkabi joylarda ishlating.context.watch<T>().stateesa holatga obuna bo'ladi va o'zgarganda vidjetni qayta quradi β lekin amaliyotda qayta qurishniBlocBuilderbilan, kerakli qism atrofida cheklab qilgan ma'qul.
3. Bloc β endi event'lar bilan¶
Cubit'da metod to'g'ridan-to'g'ri emit qiladi. Bloc esa orada bir qatlam qo'shadi: avval hodisa (Event) obyekti, keyin o'sha hodisani qayta ishlaydigan ishlovchi (handler). Nega bu qo'shimcha qatlam kerak? Chunki endi har bir niyat β "Increment", "Decrement", "Reset" β alohida, nomli obyekt bo'ladi. Ularni jurnalga yozish, kuzatish, qaytadan o'ynatish (replay) mumkin. Katta loyihada bu "audit izi" bebaho.
Avval hodisalarni e'lon qilamiz. Dart 3'ning sealed klassi (8-bob) bu yerga juda mos β barcha mumkin bo'lgan hodisalar bitta muhrlangan oilada:
// Hodisalar (event)
sealed class CounterEvent {}
final class Increment extends CounterEvent {}
final class Decrement extends CounterEvent {}
Endi Bloc'ning o'zi. Konstruktor ichida har bir hodisa tipiga on<...> bilan ishlovchi ro'yxatdan o'tkazamiz:
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<Increment>((event, emit) => emit(state + 1));
on<Decrement>((event, emit) => emit(state - 1));
}
}
Diqqat qiling:
Bloc<CounterEvent, int>β endi ikki tip kerak: hodisa tipi (CounterEvent) va holat tipi (int).on<Increment>(...)β "agarIncrementhodisasi kelsa, mana shu funksiyani ishlat". Funksiya ikki argument oladi:event(hodisaning o'zi, kerak bo'lsa undagi ma'lumotni o'qiysiz) vaemit(yangi holat chiqaradigan funksiya).- Cubit'dagi
super(0),statevaemitβ bularning hammasi Bloc'da ham xuddi shunday ishlaydi.
UI tarafida farq faqat bitta: metod chaqirish o'rniga hodisa qo'shamiz β add(...):
// Cubit edi: context.read<CounterCubit>().increment();
// Bloc bo'ldi: hodisa obyektini qo'shamiz
onPressed: () => context.read<CounterBloc>().add(Increment()),
BlocBuilder esa deyarli o'zgarmaydi β faqat tipni CounterBloc qilamiz:
BlocBuilder<CounterBloc, int>(
builder: (context, state) => Text('$state', style: const TextStyle(fontSize: 48)),
)
Cubit'mi yoki Bloc'mi?¶
Aynan bitta sanagichni ikki usulda yozdik β endi farq ko'rinib turibdi. Qaysi birini tanlash kerak?
| Cubit | Bloc | |
|---|---|---|
| Hodisa (event) | yo'q β to'g'ridan-to'g'ri metod | bor β add(Event()) |
| Kodi | kamroq, soddaroq | ko'proq "boilerplate" |
| Kuzatuv izi | metod nomi | har bir hodisa alohida obyekt |
| Qachon | oddiy holat, kam logika | murakkab, audit kerak bo'lgan oqim |
Amaliy maslahat: Cubit'dan boshlang. Ehtiyoj paydo bo'lganda (hodisalarni jurnalga yozish, debounce/throttle qilish, murakkab oqim) Bloc'ga o'tasiz. Cubit va Bloc'ni bitta loyihada bemalol aralash ishlatsa bo'ladi.
4. Holatni sealed klass bilan modellashtirish¶
Sanagichda holat β oddiy int edi. Lekin real ilovalarda holat ko'pincha bir nechta turdagi bo'ladi: "hali hech narsa yo'q", "yuklanmoqda", "ma'lumot keldi", "xato yuz berdi". Bularni bitta bool isLoading va bitta String? error bilan ifodalash chalkash va xatoga moyil β masalan, isLoading == true bo'la turib error ham to'lib qolishi mumkin.
Yechim β holatni sealed klass bilan modellashtirish. Har bir holat β alohida tur:
// foydalanuvchi modeli (soddalashtirilgan)
class User {
final String name;
User(this.name);
}
// Holatlar β muhrlangan oila
sealed class UsersState {}
final class UsersInitial extends UsersState {} // hali boshlanmagan
final class UsersLoading extends UsersState {} // yuklanmoqda
final class UsersLoaded extends UsersState { // muvaffaqiyat
final List<User> users;
UsersLoaded(this.users);
}
final class UsersError extends UsersState { // xato
final String message;
UsersError(this.message);
}
Endi UI'da switch bilan har bir holatni alohida quramiz. UsersState sealed bo'lgani uchun switch exhaustive bo'ladi β agar bir holatni unutsangiz, kompilyator darrov ogohlantiradi. Bu Bloc'ning sealed klass bilan birikkandagi "super kuchi":
BlocBuilder<UsersBloc, UsersState>(
builder: (context, state) {
return switch (state) {
UsersInitial() => const Center(child: Text('Boshlash uchun tugmani bosing')),
UsersLoading() => const Center(child: CircularProgressIndicator()),
UsersLoaded(:final users) => ListView(
children: [for (final u in users) ListTile(title: Text(u.name))],
),
UsersError(:final message) => Center(child: Text('Xato: $message')),
};
},
)
UsersLoaded(:final users) β bu 8-bobdagi object pattern: holat UsersLoaded bo'lsa, uning ichidagi users ro'yxatini darrov ajratib oladi. Endi har bir holat aniq UI'ga ega va birortasi ham unutilmaydi.
5. Asinxron oqim β foydalanuvchilarni yuklash¶
Endi hamma narsani birlashtiramiz. Real vazifa: tugma bosilganda serverdan foydalanuvchilarni yuklash. Bu yerda asinxron oqim tabiiy: avval Loading chiqaramiz, so'ng repozitoriyni (ma'lumotni qayerdan olishni biluvchi qatlam) kutamiz, natijada Loaded yoki Error chiqaramiz.
Avval hodisa β bitta "yukla" buyrug'i:
Repozitoriy β ma'lumotni qayerdan olishni biluvchi sodda klass (bu yerda taqlid qilamiz):
class UsersRepository {
Future<List<User>> fetchUsers() async {
await Future.delayed(const Duration(seconds: 1)); // tarmoq taqlidi
return [User('Ali'), User('Vali'), User('Hadicha')];
}
}
Endi Bloc. Ishlovchi async bo'ladi va jarayon davomida bir necha marta emit qiladi:
class UsersBloc extends Bloc<UsersEvent, UsersState> {
final UsersRepository repo;
UsersBloc(this.repo) : super(UsersInitial()) {
on<UsersRequested>((event, emit) async {
emit(UsersLoading()); // 1) "yuklanmoqda" holatini chiqaramiz
try {
final users = await repo.fetchUsers(); // 2) repozitoriyni kutamiz
emit(UsersLoaded(users)); // 3a) muvaffaqiyat
} catch (e) {
emit(UsersError(e.toString())); // 3b) xato
}
});
}
}
E'tibor bering: bitta ishlovchi bir necha marta emit qilishi mumkin (avval Loading, keyin Loaded/Error). Aynan shuning uchun UI avtomatik ravishda "aylanuvchi indikator" dan "ro'yxat" ga o'tadi β siz hech qayerda qo'lda almashtirmaysiz.
UI tarafini ulaymiz. Bloc'ni beramiz va tugmaga UsersRequested hodisasini biriktiramiz:
class UsersPage extends StatelessWidget {
const UsersPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => UsersBloc(UsersRepository()),
child: Scaffold(
appBar: AppBar(title: const Text('Foydalanuvchilar')),
body: BlocBuilder<UsersBloc, UsersState>(
builder: (context, state) => switch (state) {
UsersInitial() => const Center(child: Text('"Yukla" tugmasini bosing')),
UsersLoading() => const Center(child: CircularProgressIndicator()),
UsersLoaded(:final users) => ListView(
children: [for (final u in users) ListTile(title: Text(u.name))],
),
UsersError(:final message) => Center(child: Text('Xato: $message')),
},
),
floatingActionButton: Builder(
builder: (context) => FloatingActionButton(
onPressed: () => context.read<UsersBloc>().add(UsersRequested()),
child: const Icon(Icons.refresh),
),
),
),
);
}
}
Nega
Builder?context.read<UsersBloc>()ishlashi uchun kontekstBlocProviderostida bo'lishi kerak. Bu yerdaBlocProviderto'g'ridan-to'g'riScaffoldustida turibdi, shuning uchunfloatingActionButtongaBuilderorqali yangi, "ichkaridagi" kontekst beramiz. Amalda esa sahifani ko'pincha alohida vidjetga ajratasiz va bu muammo o'z-o'zidan yo'qoladi.
6. BlocListener va BlocConsumer β yon ta'sirlar¶
BlocBuilder UI ni qayta quradi. Lekin ba'zi narsalarni qayta qurish kerak emas β ularni bir marta bajarish kerak: snackbar ko'rsatish, boshqa ekranga o'tish, dialog ochish. Bularga yon ta'sir (side effect) deyiladi. Buning uchun BlocListener bor β u holat o'zgarganda listener ni chaqiradi, lekin hech narsa qaytarmaydi (UI qurmaydi):
BlocListener<UsersBloc, UsersState>(
listener: (context, state) {
if (state is UsersError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.message)),
);
}
},
child: const UsersView(),
)
Agar bitta joyda ham qurish, ham yon ta'sir kerak bo'lsa β BlocConsumer ikkalasini birlashtiradi:
BlocConsumer<UsersBloc, UsersState>(
listener: (context, state) {
if (state is UsersError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.message)),
);
}
},
builder: (context, state) => switch (state) {
UsersLoading() => const Center(child: CircularProgressIndicator()),
_ => const UsersView(),
},
)
buildWhen va listenWhen β optimallashtirish¶
Ba'zan har bir holat o'zgarishida qayta qurish/tinglash shart emas. buildWhen (va listenWhen) β eski va yangi holatni solishtirib, "haqiqatan qayta qurish kerakmi?" degan savolga bool qaytaradigan funksiya:
BlocBuilder<CounterBloc, int>(
// faqat son juftdan toqqa (yoki teskari) o'tganda qayta qur
buildWhen: (previous, current) => previous.isEven != current.isEven,
builder: (context, state) => Text('$state'),
)
Buni faqat o'lchab, haqiqatan zarurat bo'lganda ishlating β odatda Bloc'ning o'zi yetarlicha tejamkor.
7. Bloc ekotizimi β qisqacha¶
Bloc atrofida foydali yordamchi paketlar bor. Hozir chuqur kirmaymiz, lekin nomlarini bilib qo'ying:
bloc_testβ Bloc va Cubit'ni sinash uchun.blocTestyordamida "shu hodisa kelsa, shu holatlar ketma-ketligi chiqishi kerak" deb yozasiz:hydrated_blocβ holatni avtomatik diskka saqlaydi va ilova qayta ochilganda tiklaydi (HydratedCubit/HydratedBloc). Foydalanuvchi sozlamalari, savat kabilar uchun.- Bloc observer β barcha Bloc/Cubit'lardagi har bir o'zgarishni bitta joyda kuzatish (
Bloc.observer = ...). Nosozliklarni tuzatishda (debugging) qulay: butun ilovadagi holat oqimini jurnaldan ko'rasiz.
8. Provider, Riverpod, Bloc β qaysi birini tanlash?¶
Mana trilogiya yakuni. Uchchalasi ham holatni boshqaradi, lekin urg'usi har xil:
| Provider (24-bob) | Riverpod (25-bob) | Bloc (shu bob) | |
|---|---|---|---|
| Ruhi | sodda, ergonomik | kompilyatsiyada xavfsiz, moslashuvchan | tuzilmali, hodisaga asoslangan |
| Asosiy g'oya | InheritedWidget ustidan qulay qatlam |
global, tip-xavfsiz provayderlar | Event → State, bir yo'nalishli |
| Boilerplate | kam | o'rtacha | ko'proq (ayniqsa Bloc) |
| Kuchli tomoni | tez boshlash | xavfsizlik, sinash, ixtiyoriylik | kuzatuv izi, jamoa intizomi |
| Eng mos | kichik/o'rta ilova | yangi loyiha uchun yaxshi standart | katta jamoa, korxona, murakkab oqim |
"Eng yaxshisi" yo'q β loyiha va jamoaga moslab tanlanadi. Qisqa qaror yo'riqnomasi:
- Ilova kichik, tezda boshlamoqchimisiz? → Provider yetarli.
- Yangi loyiha, xavfsiz va moslashuvchan standart kerakmi? → Riverpod β ko'pchilik uchun yaxshi sukut tanlovi.
- Katta jamoa, murakkab biznes-mantiq, har bir o'zgarishni kuzatish/test qilish muhimmi? → Bloc β intizom va tuzilma beradi.
Eng muhimi: bittasini yaxshi o'rganing. Naqsh (UI → harakat → yangi holat → qayta qurish) hammasida bir xil β kutubxonani almashtirish keyinroq oson bo'ladi.
Mashqlar¶
Oson¶
- O'z so'zlaringiz bilan tushuntiring: Cubit va Bloc orasidagi asosiy farq nima? Qaysi biri "hodisa (event)" tushunchasini ishlatadi?
emitnima qiladi va nega holatnistate = ...deb to'g'ridan-to'g'ri o'zgartirib bo'lmaydi?context.read<T>()vacontext.watch<T>()orasidagi farqni ayting. TugmaningonPressedishlovchisida qaysi birini ishlatasiz va nega?
O'rta¶
BlocBuilder,BlocListenervaBlocConsumerβ uchalasi ham nima uchun? Snackbar ko'rsatish uchun qaysi birini ishlatish kerak va negaBlocBuilderemas?- Sanagich uchun
ResetEventqo'shing: Bloc'gaResethodisasini va uningon<Reset>ishlovchisini yozing (holatni0ga qaytarsin), so'ng UI'da uni qo'shadigan tugmani ko'rsating. - Quyidagi holat modeli sealed klass bilan qayta yozilsin va nega bu yaxshiroq ekanini tushuntiring:
Qiyin¶
UsersBlocning asinxron ishlovchisi nega bir nechtaemitchaqiradi? Tartibni (qaysi holat birinchi, qaysi keyin) tushuntiring vatry/catchning roli nimada?- Bir o'quvchi
BlocBuilderichidagiswitchgadefault:qo'shmoqchi. Nega sealed holatda buni qilmaslik afzal? Agar keyinchalik yangi holat (UsersEmpty) qo'shilsa,defaultborligi qanday muammoga olib keladi? - Loyihangizda holatni boshqarish uchun Provider, Riverpod va Bloc'dan birini tanlashingiz kerak. Loyihangizni qisqa tasvirlang (hajmi, jamoa, murakkablik) va tanlovingizni asoslang. "Eng yaxshisi yo'q" degani nimani anglatadi?
Yechimlar
1. Cubit β hodisasiz: siz to'g'ridan-to'g'ri metod chaqirasiz (increment()), u esa emit qiladi. Bloc β orada hodisa (Event) qatlami bor: UI add(Increment()) bilan hodisa qo'shadi, on<Increment> ishlovchisi uni qabul qilib emit qiladi. Demak hodisa tushunchasini Bloc ishlatadi. Cubit soddaroq, Bloc esa har bir niyatni alohida, kuzatiladigan obyektga aylantiradi.
2. emit(yangiHolat) β yangi holatni chiqaradi va shu Cubit/Bloc'ni tinglayotgan barcha UI (masalan BlocBuilder) ni qayta quradi. state = ... deb to'g'ridan-to'g'ri o'zgartirib bo'lmaydi, chunki state faqat o'qiladigan xossa β bu ataylab qilingan cheklov. Holat faqat emit orqali, bitta nazorat nuqtasidan o'zgarsa, har bir o'zgarish bashoratli va kuzatiladigan bo'ladi.
3. context.read<T>() obyektni oladi, lekin holatga obuna bo'lmaydi (qayta qurmaydi). context.watch<T>() esa obuna bo'ladi va holat o'zgarganda vidjetni qayta quradi. onPressed ishlovchisida read ni ishlatamiz β chunki u build paytida emas, faqat tugma bosilganda bir marta chaqiriladi; u yerda obuna kerak emas (va build ichida watch chaqirmaganimiz uchun watch bu yerda noo'rin bo'lardi).
4. BlocBuilder β holatga qarab UI quradi (qayta chizadi). BlocListener β holat o'zgarganda bir martalik yon ta'sir bajaradi (snackbar, navigatsiya, dialog) va UI qurmaydi. BlocConsumer β ikkalasini birlashtiradi. Snackbar uchun BlocListener (yoki BlocConsumerning listener qismi) kerak, BlocBuilder emas β chunki BlocBuilder qayta qurish uchun, snackbar esa qayta quriladigan UI emas, balki bir martalik harakat. Uni builder ichida chaqirsangiz, har qurishda (masalan ekran aylanganda) takror ko'rsatilib qolishi mumkin.
5. Hodisa va ishlovchi:
final class Reset extends CounterEvent {}
// CounterBloc konstruktori ichida:
on<Reset>((event, emit) => emit(0));
FloatingActionButton(
onPressed: () => context.read<CounterBloc>().add(Reset()),
child: const Icon(Icons.restart_alt),
)
6. Sealed klass bilan:
sealed class UsersState {}
final class UsersInitial extends UsersState {}
final class UsersLoading extends UsersState {}
final class UsersLoaded extends UsersState {
final List<User> users;
UsersLoaded(this.users);
}
final class UsersError extends UsersState {
final String message;
UsersError(this.message);
}
isLoading == true bo'la turib users ham, error ham to'lib qolishi β bu mantiqan ziddiyat. Sealed modelda har bir holat bir-birini istisno qiladi (faqat bittasi bo'la oladi), switch esa exhaustive bo'lib, birorta holatni unutib qoldirsangiz kompilyator ogohlantiradi.
7. Asinxron ishlovchi bir necha emit qiladi, chunki jarayon bosqichma-bosqich: avval emit(UsersLoading()) β UI darrov aylanuvchi indikator ko'rsatadi; keyin await repo.fetchUsers() bilan natijani kutadi; muvaffaqiyatda emit(UsersLoaded(users)), xatoda emit(UsersError(...)). Tartib: Loading → (Loaded yoki Error). try/catch β tarmoq/server xatosini tutib, ilovani qulatish o'rniga uni UsersError holatiga aylantiradi; shunda UI xatoni chiroyli ko'rsatadi va foydalanuvchi qayta urinishi mumkin.
8. default qo'shmaslik afzal, chunki sealed klass + defaultsiz switch exhaustive bo'ladi β barcha holatlar qamralganini kompilyator tekshiradi. Agar default qo'shsangiz, yangi holat (UsersEmpty) qo'shilganda kompilyator ogohlantirmaydi β UsersEmpty jimgina default ga tushib ketadi va siz uchun mo'ljallanmagan UI ko'rsatiladi (yashirin xato). defaultsiz esa kompilyator "UsersEmpty qamralmagan" deb darrov xato beradi va siz uni ataylab qayta ishlashga majbur bo'lasiz. Sealed klassning butun kuchi shunda.
9. Namuna javob: "Loyiham β 4 kishilik jamoa quradigan o'rta-katta savdo ilovasi; ko'p ekran, murakkab buyurtma/to'lov oqimi, har bir holat o'zgarishini kuzatish va test qilish muhim. Shuning uchun Bloc ni tanlayman β hodisaga asoslangan tuzilma jamoaga yagona intizom beradi va bloc_test bilan biznes-mantiqni ishonchli sinaymiz." β Asoslash to'g'ri bo'lsa, har qanday tanlov qabul qilinadi. "Eng yaxshisi yo'q" degani: har bir kutubxonaning kuchli tomoni boshqa kontekstda eng mos keladi β kichik ilovaga Bloc ortiqcha murakkablik, katta jamoaga esa Provider yetarli intizom bermasligi mumkin. To'g'ri tanlov β loyiha hajmi, jamoa va murakkablikka bog'liq, mavhum "yaxshilik"ka emas.
β¬ οΈ Oldingi: 25 β Riverpod (3.x) Β· π README Β· Keyingi: 27 β Animatsiya β‘οΈ