24 β Provider va InheritedWidget¶
β¬ οΈ Oldingi: 23 β Stream va reaktiv UI Β· π README Β· Keyingi: 25 β Riverpod (3.x) β‘οΈ
Bu bobda: mana, holat boshqaruvi (state management) β Flutter o'rganishdagi eng muhim bosqichlardan biri. 16-bobda
setStatebilan bitta widget ichidagi holatni o'zgartirishni o'rgandingiz. Lekin haqiqiy ilovada bir xil ma'lumotni (kim tizimga kirgani, savatdagi mahsulotlar, tanlangan mavzu/tema) ko'p ekran ko'rishi kerak. BunisetStatebilan qilishga urinsangiz, prop-drilling degan og'riqli muammoga duch kelasiz. Avval shu muammoni his qilamiz. Keyin Flutter'ning o'zidagi yechim poydevorini βInheritedWidgetni tushunamiz. So'ng uning qulay o'ramasi βproviderpaketini o'rganamiz:ChangeNotifier(holat saqlovchi model),ChangeNotifierProvider(modelni daraxtga joylash), va o'qish usullari βcontext.watch,context.read,context.select,Consumer. Yengilroq variant βValueNotifier+ValueListenableBuilderni ham ko'ramiz. Birgalikda savat (cart) va mavzu almashtirgich (theme switcher) quramiz. Oxirida halol gaplashamiz: Provider qachon yaxshi, qachon Riverpod (keyingi bob) yoki Bloc kerak.
Muammo: setState masshtablanmaydi¶
16-bobni eslang: setState holatni bitta widget ichida saqlaydi. Hisoblagich misolida count aynan o'sha widgetga tegishli edi va faqat o'sha widget uni ko'rardi. Bu kichik, mahalliy holat uchun ajoyib.
Endi haqiqiy ilovani tasavvur qiling β onlayn do'kon. Bizda savat (cart) bor. Savatga mahsulot qo'shilganda:
- yuqoridagi AppBar dagi savat belgisi (badge) yangi sonni ko'rsatishi kerak;
- savat ekrani ro'yxatni yangilashi kerak;
- ehtimol bosh sahifa ham "savatda 3 ta mahsulot" deb yozishi kerak.
Bu uchta joy bir xil holatni ko'rsatadi. Savat qayerda "yashaydi"? setState bilan bitta yo'l bor: holatni shu uchta widgetning umumiy ota-onasiga ko'tarish β buni 16-bobda "lifting state up" (holatni yuqoriga ko'tarish) deb atagandik. Ko'pincha bu eng yuqori widget β App ning o'zi bo'lib qoladi.
Lekin holat yuqorida bo'lsa, uni pastdagi widgetga yetkazish kerak. Flutter'da ma'lumot daraxt bo'ylab pastga konstruktor parametrlari orqali uzatiladi. Mana muammo qanday ko'rinadi:
// App'da holat bor
class App extends StatefulWidget { /* ... */ }
class _AppState extends State<App> {
CartModel cart = CartModel();
@override
Widget build(BuildContext context) {
// cart ni pastga uzatamiz...
return HomeScreen(cart: cart);
}
}
// HomeScreen cart ni ISHLATMAYDI β faqat pastga uzatadi
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key, required this.cart});
final CartModel cart;
@override
Widget build(BuildContext context) => Layout(cart: cart); // yana uzatadi
}
// Layout ham cart ni ISHLATMAYDI β faqat uzatadi
class Layout extends StatelessWidget {
const Layout({super.key, required this.cart});
final CartModel cart;
@override
Widget build(BuildContext context) => AppBarArea(cart: cart); // yana...
}
// AppBarArea ham ISHLATMAYDI β yana uzatadi
class AppBarArea extends StatelessWidget {
const AppBarArea({super.key, required this.cart});
final CartModel cart;
@override
Widget build(BuildContext context) => CartBadge(cart: cart); // nihoyat!
}
// CartBadge β NIHOYAT cart ni haqiqatan ishlatadi
class CartBadge extends StatelessWidget {
const CartBadge({super.key, required this.cart});
final CartModel cart;
@override
Widget build(BuildContext context) => Text('${cart.items.length}');
}
Sezdingizmi? HomeScreen, Layout, AppBarArea β uchtasi ham cart ni o'zi ishlatmaydi. Ular shunchaki uni qabul qiladi va pastga uzatadi. Bu β prop-drilling (so'zma-so'z: "xususiyatni burg'ulab o'tkazish"). Ma'lumot yetib borishi uchun siz uni har bir oraliq qatlamga "tashitishingiz" kerak.
Rasmda ko'rganingizdek, holat (cart) eng yuqorida, ishlatuvchi (CartBadge) eng pastda. Orasidagi har bir widget faqat "pochta tashuvchi" β ma'lumotni ushlab pastga uzatadi. Endi tasavvur qiling, ekran 8 qavat chuqur bo'lsa yoki holat o'zgarsa (CartModel o'rniga boshqa nom) β har bir konstruktorni qo'lda tuzatish kerak. Bu masshtablanmaydi.
π‘ Nega bu shunchalik yomon? Prop-drilling kodni mo'rt (fragile) qiladi: oraliq widget'lar o'zi ishlatmaydigan narsaga bog'lanib qoladi. Yangi ma'lumot qo'shsangiz (masalan,
user), yana hamma qatlamni yangilash kerak. Callback'lar (yuqoriga "qo'sh" deb signal berish) bilan kurashish ham xuddi shunday tarqoq bo'ladi. Bizga ma'lumotni oraliq qatlamlardan o'tkazmasdan to'g'ridan-to'g'ri ulashadigan mexanizm kerak.
InheritedWidget: Flutter'ning poydevori¶
Yaxshi xabar: Flutter'da bu muammoni hal qiladigan o'rnatilgan (built-in) mexanizm bor β InheritedWidget. Uning g'oyasi shunday: siz ma'lumotni daraxtning yuqori qismiga bir marta joylaysiz, va ixtiyoriy chuqurlikdagi har qanday avlod (descendant) uni context orqali to'g'ridan-to'g'ri o'qiy oladi β oraliq konstruktorlarga tegmasdan.
Aslida siz buni allaqachon ishlatgansiz! Theme.of(context) (14-bob, mavzu bobi), MediaQuery.of(context), Navigator.of(context) β bularning hammasi InheritedWidget ustiga qurilgan. Siz Theme.of(context).colorScheme deganingizda, Flutter daraxt bo'ylab yuqoriga yurib, eng yaqin Theme ni topadi va undan ranglarni oladi. Hech qanday prop-drilling yo'q.
Texnik jihatdan bu shunday ishlaydi: avlod widget context.dependOnInheritedWidgetOfExactType<T>() ni chaqiradi. Bu ikki ishni qiladi:
- Daraxt bo'ylab yuqoriga yurib,
Tturidagi eng yaqinInheritedWidgetni topadi va qaytaradi. - Shu widgetni "men senga bog'liqman" deb ro'yxatga oladi β ya'ni o'sha
InheritedWidgeto'zgarsa (yangi ma'lumot bilan), bu avlod avtomatik qayta quriladi.
Mana qo'lda yozilgan eng kichik misol (tushunish uchun β amalda kamdan-kam yozasiz):
class CartProvider extends InheritedWidget {
const CartProvider({
super.key,
required this.cart,
required super.child,
});
final CartModel cart;
// Avlodlar shu metod orqali topadi
static CartProvider of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<CartProvider>()!;
}
// Flutter buni so'raydi: ma'lumot o'zgardimi? Agar ha β kuzatuvchilar qayta quriladi
@override
bool updateShouldNotify(CartProvider oldWidget) => cart != oldWidget.cart;
}
Endi istalgan chuqurlikdagi avlod prop-drillingsiz o'qiydi:
π‘ Muhim:
InheritedWidgetβ bu sehr emas, balki Provider, Theme, MediaQuery va Riverpod ham (qisman) uning ustiga qurilgan poydevor. Lekin uni qo'lda yozish zerikarli:ofmetodi,updateShouldNotify, holat o'zgarganda butunInheritedWidgetni qaytadan yaratish (chunki u o'ziimmutable)... Buni har safar yozmaslik uchunproviderpaketi chiqdi β u xuddi shu mexanizmni qulay, oz kod bilan o'raydi.
provider paketi: qulay o'rama¶
provider β Flutter jamoasi tomonidan tavsiya etilgan, eng keng tarqalgan oddiy holat-boshqaruv yechimi. U InheritedWidget ni siz uchun yozadi. O'rnatamiz:
Bu pubspec.yaml ga qo'shadi:
π‘
^6.1.0dagi^(karet) "6.1.0 va undan yuqori, lekin 7.0.0 dan past" degani β xavfsiz kichik yangilanishlarni oladi.
Provider uch bo'lakdan iborat: model (ChangeNotifier), joylash (ChangeNotifierProvider) va o'qish (watch/read/select/Consumer). Birma-bir ko'ramiz.
1-bo'lak: ChangeNotifier β holat saqlovchi model¶
ChangeNotifier β Flutter o'zida bor oddiy sinf. U bitta vazifani bajaradi: o'ziga "tinglovchilar" (listeners) ulanishiga ruxsat beradi va notifyListeners() chaqirilganda hammasiga "men o'zgardim!" deb xabar beradi.
Modelimiz ChangeNotifier dan meros oladi (extends), holatni ichida saqlaydi, va uni o'zgartiruvchi metodlar oxirida notifyListeners() chaqiradi:
import 'package:flutter/foundation.dart';
class CartModel extends ChangeNotifier {
// Holat β TASHQARIDAN o'zgartirib bo'lmaydigan qilamiz (private + faqat o'qish)
final List<String> _items = [];
List<String> get items => List.unmodifiable(_items);
int get count => _items.length;
void addItem(String name) {
_items.add(name);
notifyListeners(); // <- "o'zgardim, kuzatuvchilar qayta qurilsin!"
}
void removeItem(String name) {
_items.remove(name);
notifyListeners();
}
void clear() {
_items.clear();
notifyListeners();
}
}
Diqqat qiling: _items β private (oldida _) va tashqariga faqat List.unmodifiable orqali ochiladi. Bu muhim qoida: holatni faqat model metodlari o'zgartirsin, chunki o'zgartirishdan keyin notifyListeners() chaqirilishi shart. Tashqaridan to'g'ridan-to'g'ri cart.items.add(...) qilsangiz, hech kim xabardor bo'lmaydi va UI yangilanmaydi.
β οΈ Eng ko'p uchraydigan xato: holatni o'zgartirib,
notifyListeners()ni unutib qoldirish. Bunda ma'lumot o'zgaradi, lekin ekran eskicha qoladi β chunki hech kimga xabar berilmagan. Har bir o'zgartiruvchi metod oxiridanotifyListeners()borligini tekshiring.
2-bo'lak: ChangeNotifierProvider β modelni daraxtga joylash¶
Endi modelni daraxtga qo'yamiz β odatda ildizga yaqin, undan pastda joylashgan barcha widgetlar ko'ra olishi uchun:
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CartModel(), // model bir marta yaratiladi
child: const MyApp(),
),
);
}
create: (context) => CartModel() β modelni bir marta yaratadi va undagi avlodlar uchun saqlaydi. Provider modelni o'zi dispose qiladi (ilova yopilganda tozalaydi), shuning uchun siz buni qo'lda qilishingiz shart emas.
Rasmda ko'rganingizdek, CartModel endi ChangeNotifierProvider ichida β daraxtning yuqorisida. Oraliq widget'lar (HomeScreen, Layout, AppBarArea) hech narsa uzatmaydi; eng pastdagi CartBadge modelni o'zi to'g'ridan-to'g'ri so'rab oladi. Prop-drilling yo'qoldi.
Bir nechta model kerak bo'lsa (savat va mavzu va foydalanuvchi), ularni alohida ChangeNotifierProvider bilan ichma-ich yozish o'rniga MultiProvider ishlatiladi β toza va tekis:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CartModel()),
ChangeNotifierProvider(create: (_) => ThemeModel()),
ChangeNotifierProvider(create: (_) => UserModel()),
],
child: const MyApp(),
),
);
}
3-bo'lak: o'qish β watch, read, select, Consumer¶
Model daraxtda. Endi uni qanday o'qiymiz? Provider bir nechta usul beradi va ular orasidagi farqni tushunish β bu bobning kaliti.
context.watch<T>() β modelni o'qiydi va unga obuna bo'ladi: model notifyListeners() chaqirsa, bu widget qayta quriladi. Faqat build metodi ichida ishlating:
@override
Widget build(BuildContext context) {
final cart = context.watch<CartModel>(); // obuna bo'ladi
return Text('Savatda: ${cart.count} ta'); // count o'zgarsa, qayta quriladi
}
context.read<T>() β modelni o'qiydi, lekin obuna bo'lmaydi (qayta qurmaydi). Buni callback'larda (onPressed, onTap) β ya'ni metod chaqirish uchun ishlating:
ElevatedButton(
onPressed: () {
context.read<CartModel>().addItem('Olma'); // shunchaki metodni chaqiramiz
},
child: const Text('Savatga qo\'shish'),
)
β οΈ Asosiy qoida (yodda tuting): -
buildichida ko'rsatish uchun βwatch(chunki o'zgarganda qayta qurilishi kerak). -onPressed/onTapichida metod chaqirish uchun βread(chunki bu yer qayta qurilmaydi;watchni callback'da ishlatish xatoga olib keladi).
context.select<T, R>() β watch ning aniqroq, tejamkor varianti. Modelning bitta maydoniga obuna bo'ladi: faqat o'sha maydon o'zgarsa qayta quriladi. Katta modelda ortiqcha qayta qurilishni kamaytiradi:
@override
Widget build(BuildContext context) {
// Faqat count o'zgarsa qayta quriladi β items ro'yxati boshqa joyi o'zgarsa, yo'q
final count = context.select<CartModel, int>((cart) => cart.count);
return Text('$count');
}
Consumer<T> β watch ga muqobil, widget ko'rinishidagi usul. U faqat o'z ichidagi qismni qayta quradi (butun build ni emas) va child orqali o'zgarmaydigan qismni optimallashtiradi:
Consumer<CartModel>(
// child β bir marta quriladi va saqlanadi (qayta qurilmaydi)
child: const Icon(Icons.shopping_cart),
builder: (context, cart, child) {
return Row(
children: [
child!, // o'zgarmas ikona β tekin qayta ishlatiladi
Text('${cart.count}'), // faqat shu qism cart o'zgarganda yangilanadi
],
);
},
)
π‘ Qachon
Consumer, qachonwatch?context.watchβ soddaroq va ko'pincha yetarli; u butunbuildmetodini model'ga obuna qiladi.Consumerβ qayta qurilishni kichikroq qismga cheklash kerak bo'lganda (kattabuildichida faqat bittaTextmodel'ga bog'liq bo'lsa) vachildoptimizatsiyasi kerak bo'lganda foydali. Ikkalasi bir xil natija beradi, farq β qayta qurish ko'lamida.
Eski koddan Provider.of<T>(context, listen: false) ham uchraydi β bu context.read<T>() ning to'liq shakli (listen: true esa watch ga teng). Yangi kodda qisqa read/watch ni afzal ko'ring.
Oqim: hammasi qanday bog'lanadi¶
Endi bo'laklarni bitta reaktiv aylanaga birlashtiramiz. Mana to'liq oqim:
Rasmdagi qadamlar:
- UI kuzatadi β widget
buildichidacontext.watch<CartModel>()bilan modelni o'qiydi va unga obuna bo'ladi. - Foydalanuvchi harakat qiladi β tugmani bosadi;
onPressedichidacontext.read<CartModel>().addItem('Olma')chaqiriladi. - Model o'zgaradi β
addItemro'yxatga qo'shadi vanotifyListeners()chaqiradi. - UI qayta quriladi β
notifyListenersxabariwatchqilgan barcha widget'larga boradi; ular yangi holat bilan qaytadan quriladi (badge1dan2ga).
Diqqat: bu yerda setState yo'q. Holat o'zgarishi haqidagi xabarni model notifyListeners() orqali beradi, Provider esa kuzatuvchi widget'larni topib qayta quradi. Bu β setState dan asosiy farq: holat endi widget ichida emas, alohida modelda yashaydi va ko'p ekran uni ulashishi mumkin.
Yengilroq variant: ValueNotifier + ValueListenableBuilder¶
Ba'zan butun model kerak emas β sizda atigi bitta qiymat o'zgaradi (masalan, hisoblagich soni yoki "yoqilgan/o'chiq" kaliti). Bunday hol uchun Flutter'da paketsiz (built-in) yengil yechim bor: ValueNotifier + ValueListenableBuilder.
ValueNotifier<T> β bitta T qiymatni saqlaydi va u o'zgarsa avtomatik xabar beradi (siz notifyListeners() yozmaysiz):
final counter = ValueNotifier<int>(0);
// O'zgartirish β .value ga yangi qiymat berish kifoya (o'zi xabar beradi)
counter.value++;
UI da uni ValueListenableBuilder bilan tinglaymiz β faqat shu builder qismi qayta quriladi:
ValueListenableBuilder<int>(
valueListenable: counter,
builder: (context, value, child) => Text('$value'),
)
π‘
ValueNotifierqachon? Bir-ikkita oddiy qiymat uchun, paket o'rnatishni xohlamaganingizda β ideal. Lekin holat o'sib, bir nechta maydon va murakkab mantiq paydo bo'lsa,ChangeNotifier+ Provider toza-roq. Qoida: kichik va lokal βValueNotifier; ulashiladigan va o'suvchi β Provider.ValueNotifierham aslidaChangeNotifierning ixtisoslashgan turi β bog'liqlikni sezdingizmi.
Arxitektura: model'ni UI dan ajrating¶
Provider'dan to'g'ri foydalanishning eng muhim qoidasi: model'ni UI dan ajrating. CartModel faqat Flutter widget'lariga emas β sof Dart sinfi bo'lsin (import 'package:flutter/foundation.dart'; yetarli, material.dart kerak emas). Shunda modelni alohida sinab ko'rish (test) oson bo'ladi va u UI dan mustaqil yashaydi.
Yana bir bosqich: modelni 21/22-boblardagi repozitoriy (repository β ma'lumot manbasi: API, lokal saqlov) bilan birlashtiring. Model UI uchun holatni ochib beradi, ma'lumotni esa repozitoriydan oladi:
class ProductsModel extends ChangeNotifier {
ProductsModel(this._repository);
final ProductRepository _repository; // 21/22-bobdagi repozitoriy
List<Product> _products = [];
List<Product> get products => List.unmodifiable(_products);
bool _loading = false;
bool get loading => _loading;
Future<void> load() async {
_loading = true;
notifyListeners(); // UI: "yuklanmoqda" spinnerini ko'rsatadi
_products = await _repository.fetchProducts(); // tarmoqdan oladi
_loading = false;
notifyListeners(); // UI: ro'yxatni ko'rsatadi
}
}
Bu β toza qatlamlash: UI (widget'lar) β model (ChangeNotifier, holat va mantiq) β repozitoriy (ma'lumot manbasi). Har bir qatlam o'z ishini biladi va alohida sinaladi.
Birgalikda: savat va mavzu almashtirgich¶
Endi hammasini birlashtirib, ikkita modelli kichik ilova quramiz: savat (badge hamma joyda yangilanadi) va mavzu almashtirgich (yorug'/qorong'i).
Avval mavzu modeli:
import 'package:flutter/material.dart';
class ThemeModel extends ChangeNotifier {
bool _isDark = false;
bool get isDark => _isDark;
ThemeMode get mode => _isDark ? ThemeMode.dark : ThemeMode.light;
void toggle() {
_isDark = !_isDark;
notifyListeners();
}
}
Ikkala modelni MultiProvider bilan ildizga joylaymiz:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CartModel()),
ChangeNotifierProvider(create: (_) => ThemeModel()),
],
child: const MyApp(),
),
);
}
MaterialApp mavzuni model'dan o'qiydi β shuning uchun bu yer model'ni watch qiladi (mavzu o'zgarsa, butun ilova qayta qurilishi va ranglar yangilanishi kerak):
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
final themeModel = context.watch<ThemeModel>(); // mavzuga obuna
return MaterialApp(
title: 'Provider demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.indigo,
brightness: Brightness.dark,
),
),
themeMode: themeModel.mode, // model qaror qiladi: yorug' yoki qorong'i
home: const ShopPage(),
);
}
}
Asosiy sahifa: AppBar'da savat badge (yangilanadi), mavzu kaliti, va mahsulot tugmasi:
class ShopPage extends StatelessWidget {
const ShopPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Do\'kon'),
actions: [
// mavzu kaliti β Switch model holatini ko'rsatadi (watch) va o'zgartiradi (read)
Switch(
value: context.watch<ThemeModel>().isDark,
onChanged: (_) => context.read<ThemeModel>().toggle(),
),
// savat badge β alohida widget (pastda)
const CartBadge(),
const SizedBox(width: 12),
],
),
body: Center(
child: ElevatedButton(
onPressed: () => context.read<CartModel>().addItem('Mahsulot'),
child: const Text('Savatga qo\'shish'),
),
),
);
}
}
Savat badge β faqat soni ga bog'liq, shuning uchun select bilan tejamkor qilamiz (savat ichi boshqa o'zgarsa, badge bekorga qayta qurilmaydi):
class CartBadge extends StatelessWidget {
const CartBadge({super.key});
@override
Widget build(BuildContext context) {
// faqat count o'zgarganda qayta quriladi
final count = context.select<CartModel, int>((cart) => cart.count);
return Padding(
padding: const EdgeInsets.all(8),
child: Chip(label: Text('Savat: $count')),
);
}
}
E'tibor bering: CartBadge ga hech qanday parametr uzatilmadi β u context orqali modelni o'zi topdi. Tugmani bossangiz, addItem chaqiriladi, CartModel notifyListeners() qiladi, va badge avtomatik Savat: 1 β Savat: 2 ga yangilanadi. Bu β bobning boshidagi prop-drilling muammosining toza yechimi.
Halol gap: Provider'ning o'rni va chegaralari¶
Provider β oddiy, yengil va eng keng tarqalgan yechim. Boshlovchilar uchun ideal: tushuntirishi oson, kam kod, setState dan tabiiy keyingi qadam. Ko'p haqiqiy ilovalar faqat Provider bilan ham mukammal ishlaydi.
Lekin u mukammal emas, va bu chegaralar keyingi bob β Riverpodga o'tishni asoslaydi:
- Tur bo'yicha qidirish (runtime lookup).
context.read<CartModel>()model'ni ishlash vaqtida, turi bo'yicha qidiradi. Agar shu turdagi Provider daraxtda yuqorida bo'lmasa, ilova ishga tushganda (kompilyatsiyada emas)ProviderNotFoundExceptionbilan qulaydi. Kompilyator sizni oldindan ogohlantirmaydi. BuildContextga bog'liqlik. Provider'ni o'qish uchun har doimcontextkerak β ya'ni model'ga faqat widget daraxti ichidan murojaat qila olasiz.contextyo'q joyda (oddiy funksiya, boshqa model) o'qish noqulay.- Bir turdan bittadan. Bir xil turdagi ikkita
CartModelni bitta daraxtda farqlash murakkab.
Riverpod (keyingi 25-bob) aynan shu muammolarni hal qilish uchun yaratilgan: u context ga bog'liq emas, qidiruvi kompilyatsiya vaqtida xavfsizroq, va testlash oson. Bloc esa β yana bir mashhur muqobil β ko'proq tuzilma (struktura) va event/state ajratimi beradi, kattaroq jamoa loyihalari uchun. Biz ularni keyingi boblarda solishtiramiz.
π‘ Maslahat: boshlovchi sifatida Provider'dan boshlang β uning g'oyalari (
ChangeNotifier,notifyListeners, daraxtga obuna) Riverpod va Bloc'da ham asos bo'lib qoladi. Provider'ni tushunsangiz, qolganlarini o'rganish ancha oson kechadi. Vaqtidan oldin eng murakkab yechimni tanlash β odatiy xato.
Keyingi qadam¶
Bu bobda holat boshqaruvining birinchi qadamini bosdingiz: prop-drilling muammosini his qildingiz, uning poydevori β InheritedWidget ni tushundingiz, va provider paketi bilan amaliy yechim qurdingiz β ChangeNotifier model, ChangeNotifierProvider/MultiProvider joylash, va watch/read/select/Consumer o'qish usullari. ValueNotifier yengil muqobilini ham ko'rdingiz, model'ni repozitoriy bilan toza qatlamlashni ham.
Keyingi 25-bobda Riverpod (3.x) bilan tanishamiz β Provider'ning bu bobda sanab o'tilgan chegaralarini (runtime lookup, context bog'liqligi) hal qiluvchi zamonaviy muqobil. Provider'da o'rgangan tushunchalaringiz o'sha yerda ham asqotadi.
Mashqlar¶
Oson¶
- O'z so'zlaringiz bilan ayting: prop-drilling nima va u nima uchun muammo?
setState(16-bob) bilan ko'p ekranda holat ulashishga urinsangiz, nega aynan shu muammoga duch kelasiz? context.watch<T>()vacontext.read<T>()orasidagi farq nima? Qaysi birinibuildichida ko'rsatish uchun, qaysi birinionPressedichida ishlatasiz va nega?ChangeNotifiermodel'ida holatni o'zgartirgandan keyin qaysi metodni chaqirish shart? Uni unutsangiz nima bo'ladi?Theme.of(context)vaMediaQuery.of(context)qaysi Flutter mexanizmi ustiga qurilgan? Bu Provider bilan qanday bog'liq?
O'rta¶
- Quyidagi
ChangeNotifiermodelini yozing:FavoritesModelβ ichidaSet<String> _idsbo'lsin,toggle(String id)metodi id bo'lsa o'chirsin, bo'lmasa qo'shsin, vabool isFavorite(String id)getter bo'lsin.notifyListeners()ni to'g'ri joyga qo'ying. Holatni tashqaridan o'zgartirishdan qanday himoyalaysiz? context.watch<CartModel>()o'rnigacontext.select<CartModel, int>((c) => c.count)ishlatishning afzalligi nima? Qaysi holatda bu farq seziladi?MultiProvidernima uchun kerak? Uch xil model (CartModel,ThemeModel,UserModel) ni ildizga joylovchimain()funksiyasini yozing.
Qiyin¶
- Bir o'quvchi savatga mahsulot qo'shyapti β
context.read<CartModel>()chaqirilganda ilovaProviderNotFoundExceptionbilan qulayapti. Eng ehtimoliy sabab nima va uni qanday tuzatasiz? Nega bu xato kompilyatsiyada emas, ishga tushganda chiqadi? ValueNotifier+ValueListenableBuilderni qachonChangeNotifier+ Provider o'rniga tanlaysiz? Bittabool"yoqilgan/o'chiq" kaliti uchunValueNotifierbilan to'liq misol yozing (e'lon, o'zgartirish, UI da ko'rsatish).- Provider'ning ikkita asosiy chegarasini ayting va har biri Riverpod'ga (25-bob) o'tishni qanday asoslashini tushuntiring. Boshlovchiga nega baribir Provider'dan boshlashni tavsiya qilamiz?
Yechimlar
1. Prop-drilling β holatni daraxtning yuqorisidan pastdagi uni ishlatuvchi widgetga yetkazish uchun har bir oraliq widget konstruktoriga parametr sifatida uzatish, garchi o'sha oraliq widgetlar holatni o'zi ishlatmasa ham. Bu muammo, chunki oraliq widgetlar o'ziga keraksiz narsaga bog'lanib qoladi, kod mo'rt bo'ladi (har o'zgarishda hamma qatlamni tuzatish kerak) va masshtablanmaydi. setState bilan ko'p ekranda holat ulashish uchun holatni umumiy otaga ko'tarish ("lifting state up") kerak β lekin u yuqorida bo'lsa, pastga yetkazish uchun aynan prop-drilling kerak bo'ladi.
2. context.watch<T>() β model'ni o'qiydi va unga obuna bo'ladi: model notifyListeners() chaqirsa, widget qayta quriladi. context.read<T>() β o'qiydi, lekin obuna bo'lmaydi (qayta qurmaydi). build ichida ko'rsatish uchun watch (chunki o'zgarganda yangilanishi kerak); onPressed/onTap callback'da metod chaqirish uchun read (chunki callback qayta qurilmaydi, obuna keraksiz β watch ni u yerda ishlatish noto'g'ri).
3. notifyListeners() ni chaqirish shart. Uni unutsangiz, ma'lumot o'zgaradi, lekin hech kimga xabar berilmaydi β kuzatuvchi widgetlar qayta qurilmaydi va ekran eski holatda qoladi (ilova "buzilgandek" tuyuladi, lekin ma'lumot aslida o'zgargan).
4. Ikkalasi ham InheritedWidget ustiga qurilgan: .of(context) daraxt bo'ylab yuqoriga yurib eng yaqin Theme/MediaQuery ni topadi va undagi qiymatni qaytaradi, prop-drillingsiz. Bog'liqlik: provider paketi ham xuddi shu InheritedWidget mexanizmini ishlatadi β Provider aslida InheritedWidget ni siz uchun qulay o'rab beradi.
5.
import 'package:flutter/foundation.dart';
class FavoritesModel extends ChangeNotifier {
final Set<String> _ids = {};
// tashqariga faqat o'qish uchun β o'zgartirib bo'lmaydigan nusxa
Set<String> get ids => Set.unmodifiable(_ids);
bool isFavorite(String id) => _ids.contains(id);
void toggle(String id) {
if (_ids.contains(id)) {
_ids.remove(id);
} else {
_ids.add(id);
}
notifyListeners(); // o'zgartirishdan keyin β shart
}
}
_ids private (_) va tashqariga faqat Set.unmodifiable orqali ochiladi, shuning uchun holatni faqat toggle metodi o'zgartiradi β bu notifyListeners() chaqirilishini kafolatlaydi.
6. context.select faqat tanlangan maydon (count) o'zgarganda qayta quradi; context.watch esa model har qanday notifyListeners() da (masalan, savatdagi mahsulot nomi o'zgarsa, lekin soni o'sha qolsa ham) qayta quradi. Afzallik β ortiqcha qayta qurilishni kamaytirish (samaradorlik). Farq, ayniqsa, katta/murakkab modelda yoki tez-tez o'zgaradigan holatda seziladi: badge faqat songa bog'liq bo'lsa, select bilan u faqat son o'zgarganda yangilanadi.
7. MultiProvider β bir nechta provider'ni ichma-ich (ugma-ichli) yozmasdan, tekis ro'yxatda joylash uchun; kod o'qilishi oson bo'ladi va chuqur joylashuv ("provider piramidasi") oldini oladi.
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CartModel()),
ChangeNotifierProvider(create: (_) => ThemeModel()),
ChangeNotifierProvider(create: (_) => UserModel()),
],
child: const MyApp(),
),
);
}
8. Eng ehtimoliy sabab: CartModel uchun ChangeNotifierProvider daraxtda yuqorida joylashtirilmagan (yoki read chaqirayotgan widget shu provider'ning avlodi emas β masalan, provider'ni juda pastga qo'ygan). Tuzatish: ChangeNotifierProvider(create: (_) => CartModel()) (yoki MultiProvider) ni read qilayotgan widgetdan yuqoriroqqa, odatda main() da runApp ichiga qo'yish. Bu xato ishga tushganda chiqadi, chunki Provider model'ni turi bo'yicha ishlash vaqtida (runtime) qidiradi β kompilyator read<CartModel>() to'g'ri tur ekanini ko'radi, lekin daraxtda mos provider bor-yo'qligini oldindan bilolmaydi.
9. ValueNotifier + ValueListenableBuilder ni bitta oddiy qiymat o'zgaradigan, paket o'rnatishni xohlamaydigan va holat lokal bo'lgan holatda tanlaysiz (hisoblagich, bitta kalit). ChangeNotifier+Provider esa bir nechta maydon, murakkab mantiq va ko'p ekranda ulashish kerak bo'lganda. Misol:
final isOn = ValueNotifier<bool>(false);
// O'zgartirish (masalan tugma onPressed da):
isOn.value = !isOn.value; // o'zi xabar beradi, notifyListeners kerak emas
// UI da ko'rsatish:
ValueListenableBuilder<bool>(
valueListenable: isOn,
builder: (context, value, child) => Switch(
value: value,
onChanged: (v) => isOn.value = v,
),
)
10. Ikkita asosiy chegara: (a) Tur bo'yicha runtime qidirish β Provider model'ni ishlash vaqtida turi bo'yicha topadi, mos provider bo'lmasa ilova ProviderNotFoundException bilan qulaydi (kompilyator oldindan ogohlantirmaydi); Riverpod qidiruvni xavfsizroq va context siz qiladi. (b) BuildContext ga bog'liqlik β Provider'ni o'qish uchun har doim context kerak, ya'ni faqat widget daraxti ichidan; Riverpod context ga bog'liq emas, shuning uchun model'larni bir-biridan va oddiy funksiyalardan o'qish osonroq, testlash ham qulay. Shunga qaramay boshlovchiga Provider tavsiya qilinadi, chunki uning tushunchalari (ChangeNotifier, notifyListeners, daraxtga obuna, watch/read) Riverpod va Bloc'da ham asos bo'lib qoladi β Provider'ni bilsangiz, qolganlarini o'rganish soddalashadi.
β¬ οΈ Oldingi: 23 β Stream va reaktiv UI Β· π README Β· Keyingi: 25 β Riverpod (3.x) β‘οΈ