19 β Navigatsiya: Navigator 1.0¶
β¬ οΈ Oldingi: 18 β Ro'yxatlar va scroll Β· π README Β· Keyingi: 20 β go_router bilan deklarativ navigatsiya β‘οΈ
Bu bobda: haqiqiy ilovalar bitta ekrandan iborat emas β ro'yxatdan elementga, undan tahrirlash sahifasiga o'tasiz. Shu bobda siz ekranlar orasida qanday harakatlanishni o'rganasiz. Avval stek modelini β ekranlar bir-birining ustiga taxlanishini β tushunasiz. Keyin
Navigator.pushvaNavigator.popbilan ekran ochasiz va yopasiz;MaterialPageRoutenima ekanini bilasiz. Ma'lumotni oldinga (konstruktor orqali) va orqaga (await push+pop(natija)) uzatishni β masalan tanlovchi (picker) ekran natijani qaytarishini β to'liq misolda ko'rasiz. Nomli yo'nalishlar (routes,pushNamed), stek operatsiyalari (pushReplacement,pushAndRemoveUntil,popUntil,canPop), dialog/bottom sheet/SnackBar kabi vaqtinchalik oynalar, hamdaNavigationBar,Drawer,TabBarbilan ilova qobig'ini qurish β barchasini bosqichma-bosqich o'tamiz. Oxirida imperativNavigatorning chegaralarini ko'rib, keyingi bobdagigo_routerga zamin tayyorlaymiz.
Nega navigatsiya kerak?¶
Hozirgacha qurgan ilovalaringiz bitta ekranda ishladi. Lekin haqiqiy ilovani o'ylang: Telegram'da chatlar ro'yxati bor β bittasini bossangiz, suhbat ekrani ochiladi; profilga bossangiz β profil ekrani; orqaga bossangiz β yana ro'yxatga qaytasiz. Ilova ekrandan ekranga o'tib turadi.
Navigatsiya (navigation) β bu ekranlar (Flutter tilida ularni route, ya'ni "yo'nalish" deyiladi) orasida harakatlanishni boshqarish. Flutter'da buni Navigator degan widget bajaradi. U har bir ilovada (MaterialApp ichida) avtomatik mavjud β siz uni qo'lda yaratmaysiz, faqat undan foydalanasiz.
π‘ Termin: route (yo'nalish) β bu odatda butun bir ekran. Bu bobda "route", "ekran" va "sahifa" so'zlarini deyarli bir ma'noda ishlatamiz.
Stek modeli β eng muhim g'oya¶
Navigatsiyani tushunish uchun bitta tushunchani o'zlashtirishingiz kerak: stek (stack).
Bir uyum tarelkani tasavvur qiling. Yangi tarelkani eng ustiga qo'yasiz. Olmoqchi bo'lsangiz ham β yana eng ustidagisini olasiz. Ostidagilarga to'g'ridan-to'g'ri tegolmaysiz. Mana shu "oxirgi kirgan β birinchi chiqadi" tartibi stek deyiladi.
Flutter'da ochiq ekranlar aynan shunday stek hosil qiladi:
- Ilova ochilganda stekda bitta ekran bor β birinchi (ildiz) ekran.
- Yangi ekranga o'tsangiz, u stek ustiga qo'shiladi β bu push ("itarish").
- Orqaga qaytsangiz, eng ustdagi ekran olib tashlanadi β bu pop ("chiqarish").
- Ekranda doim stek eng ustidagi ekran ko'rinadi.
Bu xuddi brauzer tarixiga o'xshaydi: yangi sahifaga o'tganingizda u tarixga qo'shiladi, "orqaga" tugmasi sizni avvalgisiga qaytaradi. Aynan shu sabab telefon va AppBardagi orqaga tugmasi sizdan hech narsa so'ramay ishlaydi β u shunchaki stek ustidagi ekranni pop qiladi.
Navigator.push va Navigator.pop¶
Eng asosiy ikki amalni ko'ramiz. Avval ikkita oddiy ekran tayyorlaymiz:
import 'package:flutter/material.dart';
class BirinchiEkran extends StatelessWidget {
const BirinchiEkran({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Birinchi ekran')),
body: Center(
child: ElevatedButton(
child: const Text('Ikkinchi ekranga o\'tish'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const IkkinchiEkran()),
);
},
),
),
);
}
}
class IkkinchiEkran extends StatelessWidget {
const IkkinchiEkran({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Ikkinchi ekran')),
body: Center(
child: ElevatedButton(
child: const Text('Orqaga'),
onPressed: () {
Navigator.pop(context);
},
),
),
);
}
}
Bu yerda eng muhim ikki satr:
// Yangi ekranni stek ustiga qo'yish (push):
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const IkkinchiEkran()),
);
// Stek ustidagi ekranni olib tashlash (pop) β orqaga qaytish:
Navigator.pop(context);
Buni bo'lib tushunamiz:
Navigator.push(context, ...)β birinchi argumentcontext(widgetning daraxtdagi o'rni; u orqali Flutter qaysiNavigatordan foydalanishni biladi). Ikkinchi argument β qaysi routeni ochish.MaterialPageRouteβ bu "route"ning bir turi. U sizga platforma uslubidagi o'tish animatsiyasini beradi: Android'da sahifa pastdan ko'tariladi, iOS'da o'ngdan suriladi. Uningbuilder:argumenti β yangi ekranni quradigan funksiya:(context) => const IkkinchiEkran().Navigator.pop(context)β stek ustidagi ekranni olib tashlaydi va oldingisiga qaytaradi.
π‘ Sizga
popko'pincha shart emas.Navigator.pushorqali ochilgan ekranningAppBarida Flutter avtomatik orqaga tugmasini (β) qo'yadi. Foydalanuvchi uni bossa yoki telefonning orqaga tugmasini bossa β Flutter o'zipopqiladi. Yuqoridagi "Orqaga" tugmasi shunchakipopni qo'lda ko'rsatish uchun.
MaterialPageRoute(builder: (context) => ...) ko'rinishini birinchi ko'rganda biroz og'irroq tuyulishi mumkin, lekin u har doim bir xil β yodlab qoldirasiz: push qil, MaterialPageRoute ber, builder ichida ekranni qaytar.
Ma'lumotni oldinga uzatish β konstruktor orqali¶
Odatda yangi ekranga biror narsa bilan o'tasiz: mahsulotlar ro'yxatidan bittasini bossangiz, detal ekran aynan shu mahsulotni ko'rsatishi kerak. Buni qilishning eng toza usuli β ma'lumotni konstruktor orqali uzatish.
Tasavvur qiling, bizda oddiy mahsulot bor:
Detal ekran konstruktorida shu mahsulotni talab qiladi:
class MahsulotDetal extends StatelessWidget {
final Mahsulot mahsulot; // konstruktordan keladi
const MahsulotDetal({super.key, required this.mahsulot});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(mahsulot.nomi)),
body: Center(
child: Text(
'${mahsulot.nomi} β ${mahsulot.narxi} so\'m',
style: const TextStyle(fontSize: 20),
),
),
);
}
}
Ro'yxat ekranida esa push qilayotganda mahsulotni shunchaki konstruktorga beramiz:
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MahsulotDetal(mahsulot: bosilgan),
),
);
}
Mana shu β eng sodda va eng ishonchli yo'l. Nega? Chunki MahsulotDetal konstruktorida required this.mahsulot turibdi β agar mahsulotni bermasangiz, kod hatto kompilyatsiya bo'lmaydi. Ya'ni "ekranga ma'lumot berishni unutish" xatosi bo'lishi mumkin emas; Dart tipi sizni himoya qiladi.
π‘ Qoida: ekranga ma'lumotni odatda konstruktor orqali bering. Bu tip-xavfsiz (type-safe) β IDE sizga avtomatik to'ldirishni ko'rsatadi, xato qiymat bersangiz darhol ogohlantiradi. Pastda ko'radigan "nomli yo'nalish" usuli moslashuvchanroq, lekin tip tekshiruvini yo'qotadi.
Ma'lumotni orqaga qaytarish β await push va pop(natija)¶
Endi teskari yo'nalishni ko'ramiz. Ko'pincha yangi ekran biror natija qaytarishi kerak. Klassik misol β tanlovchi (picker): foydalanuvchi alohida ekranda rang/shahar/mahsulotni tanlaydi, keyin tanlovi bilan oldingi ekranga qaytadi.
Buning siri ikki narsada:
- Push natija qaytaradi (
Futureko'rinishida), shuning uchun uniawaitbilan kutamiz. - Yangi ekran
popqilganda qiymat ham beradi:Navigator.pop(context, qiymat).
Avval tanlovchi ekran β bu yerda har bir element bosilganda o'z qiymati bilan pop qiladi:
class RangTanlash extends StatelessWidget {
const RangTanlash({super.key});
@override
Widget build(BuildContext context) {
const ranglar = ['Qizil', 'Yashil', 'Ko\'k'];
return Scaffold(
appBar: AppBar(title: const Text('Rang tanlang')),
body: ListView(
children: [
for (final rang in ranglar)
ListTile(
title: Text(rang),
onTap: () {
// tanlangan rangni qaytarib, bu ekranni yopamiz:
Navigator.pop(context, rang);
},
),
],
),
);
}
}
Endi chaqiruvchi ekran β u tanlovchini await bilan ochadi va qaytgan natijani oladi:
class Sozlamalar extends StatefulWidget {
const Sozlamalar({super.key});
@override
State<Sozlamalar> createState() => _SozlamalarState();
}
class _SozlamalarState extends State<Sozlamalar> {
String tanlanganRang = 'tanlanmagan';
Future<void> _rangniTanla() async {
final natija = await Navigator.push<String>(
context,
MaterialPageRoute(builder: (context) => const RangTanlash()),
);
// Foydalanuvchi tanlamasdan orqaga bossa, natija null bo'ladi:
if (natija != null) {
setState(() => tanlanganRang = natija);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Sozlamalar')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Tanlangan rang: $tanlanganRang',
style: const TextStyle(fontSize: 18)),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _rangniTanla,
child: const Text('Rang tanlash'),
),
],
),
),
);
}
}
Bu yerda nimalar bo'lyapti:
await Navigator.push<String>(...)β pushFutureqaytaradi.<String>Flutter'ga "menStringnatija kutyapman" deydi.awaitesa foydalanuvchi tanlovchi ekrandan qaytguncha kutadi.- Tanlovchida
Navigator.pop(context, rang)chaqirilganda β aynan shurangqiymatiawaitnatijasi bo'lib qaytadi. - Agar foydalanuvchi hech narsa tanlamay orqaga tugmasini bossa,
popqiymatsiz ishlaydi va natijanullbo'ladi β shuning uchunif (natija != null)tekshiruvi muhim.
β οΈ Eng ko'p uchraydigan xato:
awaitni unutish.final natija = Navigator.push(...)(await'siz) yozsangiz,natijaFuturebo'lib qoladi-yu, ammo qiymat hech qachon "ochilmaydi". Natija kutayotgan bo'lsangiz β doimawaitqo'ying (va funksiyaniasyncqiling).
Nomli yo'nalishlar (named routes)¶
Hozirgacha har safar MaterialPageRoute(builder: ...) yozdik. Kichik ilovada bu yetarli. Lekin ekranlar ko'paysa, har biriga nom (masalan /profil, /sozlamalar) berib, bir joyda ro'yxat qilish qulayroq bo'lishi mumkin.
Yo'nalishlarni MaterialAppda e'lon qilasiz:
MaterialApp(
initialRoute: '/', // ilova ochilganda
routes: {
'/': (context) => const BoshEkran(),
'/profil': (context) => const ProfilEkran(),
'/sozlamalar': (context) => const Sozlamalar(),
},
);
Endi nom orqali o'tasiz:
Nomli yo'nalishga ham ma'lumot uzatish mumkin β arguments orqali:
Qabul qiluvchi ekranda esa uni ModalRoute orqali o'qiysiz:
@override
Widget build(BuildContext context) {
final mahsulot = ModalRoute.of(context)!.settings.arguments as Mahsulot;
return Scaffold(
appBar: AppBar(title: Text(mahsulot.nomi)),
// ...
);
}
E'tibor bering β bu yerda as Mahsulot deb qo'lda tipga aylantirish kerak, chunki arguments ning tipi Object? (ya'ni "har qanday narsa"). Agar noto'g'ri tip bersangiz, xato faqat ishlash vaqtida (runtime) chiqadi.
Konstruktor vs nomli yo'nalish:
Konstruktor (MahsulotDetal(mahsulot: x)) |
Nomli (pushNamed('/detal', arguments: x)) |
|
|---|---|---|
| Tip xavfsizligi | β to'liq, kompilyatsiya vaqtida tekshiriladi | β Object?, qo'lda as kerak |
| Markazlashtirish | yo'nalishlar tarqoq | hammasi routes: da bir joyda |
| Eng yaxshi qachon | aksariyat holatlar | juda ko'p ekranli ilovada |
π‘ Muhim: 2026-yilda jiddiy ilovalarda nomli yo'nalishlar o'rniga ko'pincha
go_routerishlatiladi β u veb-URL'lar, deep link va murakkab oqimlarni ancha yaxshi boshqaradi. Buni keyingi 20-bobda o'rganamiz. Hozircha asosiy g'oyani β "yo'nalishga nom berish mumkin" β bilsangiz kifoya.
Stek operatsiyalari β pushdan boshqalar¶
push va pop β eng ko'p ishlatiladigani, lekin yana bir nechta foydali amal bor. Ularning hammasi shu stekni boshqaradi.
pushReplacement β ustidagini almashtirish¶
Joriy ekranni stekdan olib tashlab, o'rniga yangisini qo'yadi. Klassik misol: login β home. Login muvaffaqiyatli bo'lganda, foydalanuvchi orqaga bosib login ekraniga qaytmasligi kerak:
pushAndRemoveUntil β stekni tozalash¶
Yangi ekran qo'shadi va undan oldingilarni o'chiradi. Klassik misol: logout β login. Chiqishdan keyin butun ilova ichidagi ekranlar stekdan ketishi va faqat login qolishi kerak:
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => const LoginEkran()),
(route) => false, // false => barcha eski ekranlarni olib tashla
);
popUntil β bir nechta ekranni orqaga¶
Stekdan ekranlarni shartga yetguncha pop qiladi. Masalan, chuqurroq kirib ketgan bo'lsangiz, to'g'ridan-to'g'ri bosh ekranga qaytish:
canPop va maybePop β orqaga qaytish mumkinmi?¶
Navigator.canPop(context) β stekda olib tashlanadigan ekran bormi-yo'qligini tekshiradi (true/false). Agar siz birinchi (ildiz) ekrandasiz, pop qiladigan narsa yo'q. Navigator.maybePop(context) esa β "mumkin bo'lsa pop qil" degani: agar orqaga qaytish mumkin bo'lsa pop qiladi, bo'lmasa hech narsa qilmaydi (xato chiqarmaydi).
β οΈ Birinchi ekranda
popqilmang. Stekda bitta ekran qolgandaNavigator.popqilsangiz β ilova "bo'sh" holatga tushib qolishi mumkin. Shubha bo'lsamaybePopishlating yoki avvalcanPopbilan tekshiring.
Vaqtinchalik oynalar: dialog, bottom sheet, SnackBar¶
Ba'zan butun ekranni almashtirmasdan, ustiga kichik oyna chiqarmoqchi bo'lasiz: tasdiqlash so'rovi, tanlov menyusi yoki qisqa xabar. Qizig'i shundaki, bular ham ichida route β ular ham Navigator stekiga (overlay sifatida) qo'yiladi, shuning uchun ularni ham pop bilan yopasiz.
showDialog + AlertDialog¶
Tasdiqlash oynasi β "Rostdan ham o'chirmoqchimisiz?" β natija qaytaradi (push kabi):
Future<void> _ochirishniTasdiqla() async {
final javob = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('O\'chirish'),
content: const Text('Bu elementni o\'chirmoqchimisiz?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false), // yo'q
child: const Text('Bekor qilish'),
),
FilledButton(
onPressed: () => Navigator.pop(context, true), // ha
child: const Text('O\'chirish'),
),
],
),
);
if (javob == true) {
// haqiqatdan o'chirish kodi shu yerda
}
}
E'tibor bering β bu xuddi push/pop kabi: await showDialog<bool>(...) natijani kutadi, dialog ichidagi tugmalar Navigator.pop(context, true/false) bilan javobni qaytaradi.
π‘
SimpleDialogβ buAlertDialogning soddaroq turi: tasdiqlash o'rniga variantlar ro'yxatini ko'rsatish uchun (SimpleDialogOptionbilan). Foydalanuvchi variantni bossa, sizNavigator.pop(context, tanlangan)bilan qaytarasiz.
showModalBottomSheet¶
Ekranning pastidan ko'tariladigan panel β masalan "ulashish" yoki amal menyusi:
showModalBottomSheet(
context: context,
builder: (context) => Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.share),
title: const Text('Ulashish'),
onTap: () => Navigator.pop(context),
),
ListTile(
leading: const Icon(Icons.delete),
title: const Text('O\'chirish'),
onTap: () => Navigator.pop(context),
),
],
),
),
);
SnackBar β qisqa xabar¶
Ekran pastida bir necha soniya ko'rinib yo'qoladigan kichik xabar (masalan "Saqlandi"). Bu Navigator orqali emas, ScaffoldMessenger orqali ko'rsatiladi:
π‘ Nega
ScaffoldMessenger? Agar SnackBar to'g'ridan-to'g'riScaffoldga bog'langanda, ekran almashganda u darhol yo'qolib ketardi.ScaffoldMessengeresa undan ustun turadi β shuning uchun ekran o'zgarsa ham xabar tinch ko'rinib turaveradi.
Ilova qobig'i: NavigationBar, Drawer, TabBar¶
Yuqoridagilar bir yo'nalishli harakat edi (ichkariga kirib-chiqasiz). Lekin ko'p ilovalarda bir darajadagi asosiy bo'limlar bor: "Asosiy", "Qidiruv", "Profil". Bularning orasida o'tish β bu push/pop emas (stek o'zgarmaydi), shunchaki ko'rsatilayotgan bo'limni almashtirish.
Pastki NavigationBar (Material 3)¶
Material 3'da pastki navigatsiya uchun NavigationBar ishlatiladi (eski BottomNavigationBar ham hali ishlaydi, lekin yangi ilovada NavigationBar afzal). Joriy bo'lim indeks (0, 1, 2...) bilan belgilanadi; foydalanuvchi bo'limni bossa, indeksni o'zgartiramiz va body shunga mos sahifani ko'rsatadi.
Mana 3 bo'limli to'liq qobiq. IndexedStack uch sahifani saqlaydi va faqat tanlanganini ko'rsatadi:
import 'package:flutter/material.dart';
class IlovaQobigi extends StatefulWidget {
const IlovaQobigi({super.key});
@override
State<IlovaQobigi> createState() => _IlovaQobigiState();
}
class _IlovaQobigiState extends State<IlovaQobigi> {
int _index = 0; // joriy bo'lim
// Har bo'lim uchun bittadan sahifa:
static const _sahifalar = [
Center(child: Text('Asosiy', style: TextStyle(fontSize: 24))),
Center(child: Text('Qidiruv', style: TextStyle(fontSize: 24))),
Center(child: Text('Profil', style: TextStyle(fontSize: 24))),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Mening ilovam')),
// IndexedStack: barcha sahifani saqlaydi, faqat _index'dagisini ko'rsatadi
body: IndexedStack(index: _index, children: _sahifalar),
bottomNavigationBar: NavigationBar(
selectedIndex: _index,
onDestinationSelected: (yangiIndex) {
setState(() => _index = yangiIndex);
},
destinations: const [
NavigationDestination(icon: Icon(Icons.home), label: 'Asosiy'),
NavigationDestination(icon: Icon(Icons.search), label: 'Qidiruv'),
NavigationDestination(icon: Icon(Icons.person), label: 'Profil'),
],
),
);
}
}
Asosiy g'oya: selectedIndex joriy bo'limni ko'rsatadi, onDestinationSelected bo'lim bosilganda chaqiriladi va biz setState bilan _indexni yangilaymiz. IndexedStack shu indeksdagi sahifani ko'rsatadi.
π‘ Nega
IndexedStack, oddiy almashtirish emas?IndexedStackbarcha sahifalarni xotirada saqlab turadi, faqat bittasini ko'rsatadi. Shuning uchun bir bo'limda ro'yxatni pastga scroll qilib, boshqasiga o'tib qaytsangiz β scroll holati yo'qolmaydi. Agarbody: _sahifalar[_index]deb yozsangiz, har safar sahifa noldan quriladi va holat ketadi.
Drawer β yon menyu¶
Scaffoldga drawer: bersangiz, chetdan suriladigan yon menyu paydo bo'ladi va AppBarda uni ochadigan "hamburger" (β°) tugmasi avtomatik chiqadi:
Scaffold(
appBar: AppBar(title: const Text('Bosh sahifa')),
drawer: Drawer(
child: ListView(
children: [
const DrawerHeader(child: Text('Menyu')),
ListTile(
title: const Text('Sozlamalar'),
onTap: () {
Navigator.pop(context); // avval drawer'ni yop
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const Sozlamalar()),
);
},
),
],
),
),
body: const Center(child: Text('Asosiy mazmun')),
);
E'tibor bering: drawer'dagi elementni bosganda avval Navigator.pop(context) bilan drawer'ni yopamiz, keyin kerakli ekranga push qilamiz.
TabBar β yuqori yorliqlar¶
Bir sahifa ichida ufqiy yorliqlar (masalan "Yangiliklar / Mashhur / Sevimlilar") uchun TabBar + TabBarView + DefaultTabController ishlatiladi:
DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: const Text('Yangiliklar'),
bottom: const TabBar(
tabs: [
Tab(text: 'Yangi'),
Tab(text: 'Mashhur'),
Tab(text: 'Sevimli'),
],
),
),
body: const TabBarView(
children: [
Center(child: Text('Yangi maqolalar')),
Center(child: Text('Mashhur maqolalar')),
Center(child: Text('Sevimli maqolalar')),
],
),
),
);
DefaultTabController yorliqlar bilan TabBarView orasidagi bog'lanishni o'zi boshqaradi β length: yorliqlar soniga teng bo'lishi kerak.
Imperativ Navigatorning chegaralari¶
Bu bobda ko'rgan usul imperativ deyiladi: siz qadam-baqadam "buni push qil, buni pop qil" deysiz. Kichik va o'rta ilovalar uchun bu juda yaxshi ishlaydi.
Lekin ilova o'sgan sari ba'zi muammolar paydo bo'ladi:
- Veb-URL'lar. Flutter web'da har bir ekranga to'g'ri manzil (
/mahsulot/42) berish, foydalanuvchi URL'ni qo'lda yozsa to'g'ri ekran ochilishi β imperativNavigatorda murakkab. - Deep link (chuqur havola). Bildirishnoma bosilganda to'g'ridan-to'g'ri aniq ekranga (va stek to'g'ri tuzilgan holatda) kirish.
- Murakkab oqimlar. "Agar login bo'lmasa, qaerga bormoqchi bo'lsa ham login ekraniga yo'naltir" kabi shartli yo'naltirishlar tarqoq bo'lib ketadi.
Aynan shu muammolarni hal qilish uchun deklarativ navigatsiya β go_router paketi mavjud. U "joriy holat shu bo'lsa, ekranlar steki shunaqa ko'rinadi" deb tasvirlaydi (xuddi UI = f(holat) g'oyasidek). Buni keyingi 20-bobda batafsil o'rganamiz.
π‘ Lekin imperativ
Navigatorni bilish baribir muhim βgo_routerham ichida xuddi shu stek modelida ishlaydi, dialog/bottom sheet/SnackBar esa har doim aynan shu bobdagidek qoladi.
Eng ko'p uchraydigan xatolar¶
awaitni unutish. Push'dan natija kutsangiz,final r = await Navigator.push(...)yozing va funksiyaniasyncqiling.awaitsiz natija hech qachon kelmaydi.popdan keyin eskicontextdan foydalanish. Ekran yopilgach (popdan keyin) o'sha ekranningcontexti endi yaroqsiz. Shuning uchunpopqilib, keyin darhol o'sha context bilan boshqa ishni (masalanScaffoldMessenger.of(context)) qilmang β bu xatoga olib keladi.- Birinchi ekranda
pop. Stekda bitta ekran qolgandapopqilmang; shubha bo'lsamaybePopishlating. - Orqaga tugmasini hisobga olmaslik. Foydalanuvchi har doim telefonning orqaga tugmasini bosishi mumkin β ekraningiz shunga tayyor bo'lsin (masalan to'ldirilmagan forma yo'qolib qolmasin).
Keyingi qadam¶
Endi siz ilovani bir necha ekranli qila olasiz: push/pop bilan o'tasiz, ma'lumotni oldinga (konstruktor) va orqaga (pop(natija)) uzatasiz, dialog/bottom sheet/SnackBar chiqarasiz va NavigationBar/Drawer/TabBar bilan ilova qobig'ini qurasiz.
Keyingi 20-bobda go_router bilan deklarativ navigatsiyani o'rganamiz: URL-asoslangan yo'nalishlar, deep link, shartli yo'naltirish va yana ko'p narsa β zamonaviy Flutter ilovalarining standart yondashuvi.
Mashqlar¶
Oson¶
- O'z so'zlaringiz bilan stek modelini tushuntiring:
pushvapopnima qiladi, ekranda qaysi ekran ko'rinadi? Brauzer tarixi bilan o'xshashligini ayting. Navigator.push(context, MaterialPageRoute(builder: (context) => const Sahifa()))satridagi har bir qismni izohlang:push,context,MaterialPageRoute,builder.Navigator.pushorqali ochilgan ekranningAppBarida orqaga (β) tugmasi qayerdan paydo bo'ladi? Uni bosganda nima sodir bo'ladi?- Ma'lumotni ekranga konstruktor orqali uzatish nega "nomli yo'nalish + arguments" usulidan tip jihatidan xavfsizroq?
O'rta¶
- To'liq, kompilyatsiya bo'ladigan ikki ekranli ilova yozing:
BoshEkran'da tugma bo'lsin, uIkkinchiEkranga o'tsin;IkkinchiEkran'da matn va "Orqaga" tugmasi bo'lsin. pushReplacementvapushAndRemoveUntilfarqini misol bilan tushuntiring: qaysi biri login uchun, qaysi biri logout uchun va nega?- 3 bo'limli (
Asosiy/Qidiruv/Profil)NavigationBarli ilova qobig'ini yozing. NegaIndexedStackishlatamiz?
Qiyin¶
- Tanlovchi (picker) ekran yozing: chaqiruvchi ekranda tanlangan shahar ko'rinsin; tugma bosilganda alohida ekran ochilib, shaharlar ro'yxatidan bittasi tanlansin va u
pop(natija)orqali qaytsin.awaitvanullholatini to'g'ri ishlang. - Foydalanuvchi "O'chirish" bosganda
AlertDialogbilan tasdiq so'ralsin (Ha/Yo'q), keyin natijaga qarabSnackBarda "O'chirildi" yoki hech narsa ko'rsatilsin.showDialog<bool>vaNavigator.pop(context, true/false)ishlating. - Bir o'quvchi: "
Navigator.push(...)yozdim, lekin tanlangan qiymat hech qachon kelmayapti" deyapti. Ehtimoliy ikki sababni (await yo'q; ekranpop(qiymat)o'rniga shunchakipop()qilgan) tushuntiring va tuzatishni ko'rsating.
Yechimlar
1. Stek β "oxirgi kirgan birinchi chiqadi" tartibidagi taxlanma. push yangi ekranni stek ustiga qo'yadi; pop esa eng ustdagini olib tashlaydi. Ekranda doim stek eng ustidagi ekran ko'rinadi. Brauzer tarixi ham shunday: yangi sahifaga o'tsangiz u tarixga qo'shiladi (push), "orqaga" tugmasi sizni avvalgisiga qaytaradi (pop).
2.
- push β yangi ekranni stek ustiga qo'yadigan amal.
- context β widgetning daraxtdagi o'rni; u orqali Flutter qaysi Navigatordan foydalanishni biladi.
- MaterialPageRoute β platforma uslubidagi o'tish animatsiyasini beradigan route turi.
- builder β yangi ekranni quradigan funksiya; (context) => const Sahifa() ko'rinishida.
3. Bu tugmani Flutter avtomatik qo'shadi β chunki ekran push orqali ochilgan, ya'ni stekda uning ostida boshqa ekran bor. Tugmani bosganda Flutter Navigator.pop(context) ni chaqiradi va oldingi ekranga qaytaradi.
4. Konstruktor usulida (MahsulotDetal(mahsulot: x)) parametr aniq tipga ega va required bo'lishi mumkin β agar bermasangiz yoki noto'g'ri tip bersangiz, kod kompilyatsiya bo'lmaydi. Nomli yo'nalishda arguments tipi Object? bo'lib, qabul qiluvchida as Mahsulot bilan qo'lda aylantiriladi β xato faqat ishlash vaqtida (runtime) chiqadi va e'tibordan chetda qolishi mumkin.
5.
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: BoshEkran()));
class BoshEkran extends StatelessWidget {
const BoshEkran({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Bosh ekran')),
body: Center(
child: ElevatedButton(
child: const Text('Ikkinchi ekranga'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const IkkinchiEkran()),
);
},
),
),
);
}
}
class IkkinchiEkran extends StatelessWidget {
const IkkinchiEkran({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Ikkinchi ekran')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Salom, bu ikkinchi ekran!'),
const SizedBox(height: 16),
ElevatedButton(
child: const Text('Orqaga'),
onPressed: () => Navigator.pop(context),
),
],
),
),
);
}
}
6. pushReplacement joriy ekranni olib tashlab o'rniga yangisini qo'yadi β bitta ekranni almashtiradi. Login uchun mos: login muvaffaqiyatli bo'lganda uni home bilan almashtiramiz, shunda foydalanuvchi orqaga bosib login'ga qaytmaydi. pushAndRemoveUntil esa yangi ekran qo'yadi va undan oldingilarning hammasini ((route) => false) o'chiradi β logout uchun mos: chiqishdan keyin ilova ichidagi barcha ekranlar ketib, faqat login qoladi.
7.
import 'package:flutter/material.dart';
class Qobiq extends StatefulWidget {
const Qobiq({super.key});
@override
State<Qobiq> createState() => _QobiqState();
}
class _QobiqState extends State<Qobiq> {
int _index = 0;
static const _sahifalar = [
Center(child: Text('Asosiy')),
Center(child: Text('Qidiruv')),
Center(child: Text('Profil')),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Ilova')),
body: IndexedStack(index: _index, children: _sahifalar),
bottomNavigationBar: NavigationBar(
selectedIndex: _index,
onDestinationSelected: (i) => setState(() => _index = i),
destinations: const [
NavigationDestination(icon: Icon(Icons.home), label: 'Asosiy'),
NavigationDestination(icon: Icon(Icons.search), label: 'Qidiruv'),
NavigationDestination(icon: Icon(Icons.person), label: 'Profil'),
],
),
);
}
}
IndexedStack barcha sahifalarni xotirada saqlaydi va faqat tanlanganini ko'rsatadi, shuning uchun bo'limlar orasida o'tganda har birining holati (scroll, kiritilgan matn) yo'qolmaydi.
8.
import 'package:flutter/material.dart';
class ShaharSozlama extends StatefulWidget {
const ShaharSozlama({super.key});
@override
State<ShaharSozlama> createState() => _ShaharSozlamaState();
}
class _ShaharSozlamaState extends State<ShaharSozlama> {
String _shahar = 'tanlanmagan';
Future<void> _tanla() async {
final natija = await Navigator.push<String>(
context,
MaterialPageRoute(builder: (context) => const ShaharRoyxat()),
);
if (natija != null) {
setState(() => _shahar = natija);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Manzil')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Shahar: $_shahar', style: const TextStyle(fontSize: 18)),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _tanla,
child: const Text('Shahar tanlash'),
),
],
),
),
);
}
}
class ShaharRoyxat extends StatelessWidget {
const ShaharRoyxat({super.key});
@override
Widget build(BuildContext context) {
const shaharlar = ['Toshkent', 'Samarqand', 'Buxoro'];
return Scaffold(
appBar: AppBar(title: const Text('Shahar tanlang')),
body: ListView(
children: [
for (final s in shaharlar)
ListTile(
title: Text(s),
onTap: () => Navigator.pop(context, s),
),
],
),
);
}
}
await natijani kutadi; foydalanuvchi tanlamay orqaga bossa, natija null bo'ladi va if (natija != null) uni e'tiborsiz qoldiradi.
9.
Future<void> _ochirish(BuildContext context) async {
final javob = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('O\'chirish'),
content: const Text('Bu elementni o\'chirmoqchimisiz?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('Yo\'q'),
),
FilledButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('Ha'),
),
],
),
);
if (javob == true) {
// o'chirish amali...
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('O\'chirildi')),
);
}
}
showDialog<bool> natijani kutadi; tugmalar pop(true/false) bilan javob qaytaradi. javob == true bo'lgandagina SnackBar ko'rsatamiz (null β foydalanuvchi tashqariga bosib dialog'ni yopgani).
10. Ikki ehtimoliy sabab:
- await yo'q. final r = Navigator.push(...) yozilgan bo'lsa, r β Future, qiymat emas. Tuzatish: final r = await Navigator.push<String>(...) va funksiya async bo'lsin.
- Ekran qiymatsiz pop qilgan. Tanlovchi ekran Navigator.pop(context) (qiymatsiz) chaqirgan bo'lsa, natija doim null keladi. Tuzatish: Navigator.pop(context, tanlanganQiymat) deb qiymat bilan qaytarish.
β¬ οΈ Oldingi: 18 β Ro'yxatlar va scroll Β· π README Β· Keyingi: 20 β go_router bilan deklarativ navigatsiya β‘οΈ