3-qism · Amaliy Flutter·08-bob
Navigatsiya va routing — Navigator 2.0 va go_router
Navigator 1.0'ning imperativ push/pop modelidan Navigator 2.0'ning deklarativ Router arxitekturasigacha, va amaliyotda ular ustiga qurilgan go_router qanday ishlaydi.
Navigatsiya — Flutter'da eng ko'p "nazariyada tushunaman, lekin amalda tushuntirib bera olmayman" bo'ladigan mavzulardan biri. Intervyuda "Navigator 1.0 va 2.0 farqini tushuntiring" degan savol — nafaqat API bilishni, balki nega bu o'zgarish kerak bo'lganini tushunishni sinaydi.
1. Navigator 1.0 — imperativ, stack asosidagi model#
Navigator 1.0 — Flutter'ning original navigatsiya modeli. U sahifalarni
klassik stek (stack) sifatida ko'radi: push — ustiga qo'yish, pop —
olib tashlash.
// To'g'ridan-to'g'ri widget bilan
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const DetailScreen()),
);
// Nomlangan marshrut bilan
Navigator.pushNamed(context, '/detail', arguments: productId);
Navigator.pop(context);Nomlangan marshrutlar MaterialApp'da ro'yxatdan o'tkaziladi:
MaterialApp(
routes: {
'/': (context) => const HomeScreen(),
'/detail': (context) => const DetailScreen(),
},
)Bu model sodda va tushunarli, lekin uchta jiddiy cheklovi bor:
- Deklarativ emas — sahifalar stekini "hozirgi holat" sifatida tasvirlab
bo'lmaydi, faqat ketma-ket buyruqlar (
push,pop) orqali o'zgartiriladi. Bu deep link kabi "darhol muayyan holatga o'tish" stsenariylarini qiyin qiladi. - Veb URL bilan sinxronlash qiyin — brauzerning orqaga/oldinga tugmalari va URL satri Navigator stekiga tabiiy ravishda mos kelmaydi.
- Orqaga tugmasini boshqarish cheklangan — Android tizim orqaga tugmasini ilova ichida murakkab holatlarda (masalan, ichki navigator ichida) nozik boshqarish qiyin.
2. Navigator 2.0 — deklarativ, Router-asoslangan model#
Navigator 2.0 (rasman "Router API") sahifalar stekini holat (state)
sifatida modellaydi — siz stekka nima qo'shish/olib tashlashni emas, balki
stek qanday ko'rinishi kerakligini tasvirlaysiz, Flutter esa farqni o'zi
hisoblab, kerakli push/pop amallarini bajaradi. Bu xuddi setState va
declarative widget qurilishiga o'xshaydi — faqat sahifalar darajasida.
Asosiy uch komponent:
Qo'lda yozilgan minimal misol:
class AppRouterDelegate extends RouterDelegate<Uri>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<Uri> {
String? selectedProductId;
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: [
const MaterialPage(child: HomeScreen()),
if (selectedProductId != null)
MaterialPage(child: DetailScreen(id: selectedProductId!)),
],
onDidRemovePage: (page) {
selectedProductId = null;
notifyListeners();
},
);
}
@override
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@override
Future<void> setNewRoutePath(Uri configuration) async {
// Deep link kelganda qanday holatga o'tish kerakligini shu yerda hal qilasiz.
}
}E'tibor bering: selectedProductId'ni null'dan qiymatga o'zgartirib
notifyListeners() chaqirish yetarli — DetailScreen avtomatik ravishda
stekka qo'shiladi, chunki pages ro'yxati endi uni o'z ichiga oladi. Bu —
imperativ push()'dan farqli, to'liq deklarativ yondashuv.
Bu qo'lda yozish darajasi ko'p loyihalar uchun ortiqcha boilerplate — shuning uchun deyarli hech kim Navigator 2.0'ni to'g'ridan-to'g'ri ishlatmaydi; o'rniga uning ustiga qurilgan kutubxonalar ishlatiladi.
3. go_router — amaliy standart#
go_router — Flutter jamoasining rasmiy tavsiya qilingan paketi, Navigator
2.0'ning murakkabligini deklarativ, URL-asoslangan konfiguratsiya orqasiga
yashiradi:
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'product/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return DetailScreen(id: id);
},
),
],
),
GoRoute(
path: '/login',
builder: (context, state) => const LoginScreen(),
),
],
);MaterialApp.router(
routerConfig: router,
)Navigatsiya endi oddiy metod chaqiruvlari orqali, lekin tagida to'liq deklarativ mexanizm bilan ishlaydi:
context.go('/product/42'); // stekni almashtiradi
context.push('/product/42'); // stekka qo'shadi (orqaga qaytish mumkin)
context.pop();Path va query parametrlar#
GoRoute(
path: '/search',
builder: (context, state) {
final query = state.uri.queryParameters['q'] ?? '';
return SearchScreen(query: query);
},
)
// context.go('/search?q=flutter')Redirect va guard'lar (autentifikatsiya himoyasi)#
GoRouter(
redirect: (context, state) {
final loggedIn = AuthState.instance.isLoggedIn;
final loggingIn = state.matchedLocation == '/login';
if (!loggedIn && !loggingIn) return '/login';
if (loggedIn && loggingIn) return '/';
return null; // o'zgarish shart emas
},
routes: [...],
)redirect har bir navigatsiyada tekshiriladi — bu autentifikatsiya holati
o'zgarganda avtomatik qayta yo'naltirish uchun markazlashgan, deklarativ joy
beradi (har bir sahifada qo'lda tekshirish o'rniga).
Ichma-ich navigatorlar (nested navigation, masalan bottom tab'lar)#
StatefulShellRoute har bir tab uchun o'z mustaqil stekini saqlash
imkonini beradi — foydalanuvchi "Katalog" tabida uch daraja chuqur bo'lib,
"Profil" tabiga o'tib qaytganda, "Katalog" tabining steki saqlanib qoladi:
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) => ScaffoldWithNavBar(navigationShell),
branches: [
StatefulShellBranch(routes: [GoRoute(path: '/catalog', ...)]),
StatefulShellBranch(routes: [GoRoute(path: '/profile', ...)]),
],
)4. Deep linking — iOS va Android'da sozlash#
Deep link — tashqi manba (push xabar, brauzer, boshqa ilova) orqali ilovaning
ma'lum bir ekraniga to'g'ridan-to'g'ri o'tish. go_router'ning
RouteInformationParser'i myapp://product/42 yoki https://myapp.com/product/42
kabi URL'ni avtomatik GoRoute'larga moslashtiradi, lekin operatsion tizim
darajasida qo'shimcha sozlash shart:
- Android:
AndroidManifest.xml'daintent-filterbilanandroid:autoVerify="true"(App Links uchun) yoki custom scheme ro'yxatga olinadi. - iOS:
Info.plist'daCFBundleURLTypes(custom scheme) yoki Universal Links uchunapple-app-site-associationfayli serverda joylashtiriladi vaAssociated Domainscapability yoqiladi.
5. Sahifalar orasida ma'lumot uzatish#
Uch xil usul bor, va qachon qaysi birini tanlash muhim:
| Usul | Qachon ishlatiladi |
|---|---|
Path/query parametr ('/product/:id') | ID kabi, deep link orqali ham kelishi mumkin bo'lgan qiymatlar |
extra parametri (context.go('/detail', extra: product)) | Deep link orqali kelmaydigan, faqat ilova ichida uzatiladigan murakkab obyekt (masalan, butun Product obyekti) |
| Global state (Provider/Riverpod) | Bir nechta, o'zaro bog'liq bo'lmagan sahifalarga kerak bo'lgan umumiy ma'lumot |
extra parametridan ehtiyot bo'lish kerak: agar foydalanuvchi to'g'ridan-to'g'ri
o'sha URL'ga (masalan, brauzerda yoki deep link orqali) kirsa, extra null
bo'ladi — chunki u faqat Dart xotirasida uzatiladi, URL'ning bir qismi emas.
Shuning uchun ekran extra yo'qligida ID orqali ma'lumotni qayta yuklay
olishi kerak (graceful fallback).
Ko'p uchraydigan xato javoblar#
| Savol/holat | Junior darajadagi javob | Senior darajadagi javob |
|---|---|---|
| "Navigator 1.0 va 2.0 farqi?" | "2.0'da GoRouter ishlatiladi" | Imperativ push/pop stekdan, Page ro'yxati asosidagi deklarativ holatga o'tishni, va bu nega deep link/veb uchun kerakligini tushuntiradi |
| Deep link ilovani ochadi, lekin noto'g'ri ekranga tushadi | Faqat Dart kodini tekshiradi | Android manifest / iOS entitlements sozlamalarini ham tekshiradi |
extra orqali uzatilgan obyekt deep link'da null bo'lib chiqadi | "Bug" deb hisoblaydi | extra faqat runtime xotirada ekanini, URL orqali kelmasligini tushuntiradi va ID-asoslangan qayta yuklashni taklif qiladi |
| Bottom tab navigatsiyasida tab almashtirilganda ichki stek yo'qoladi | Muammoni e'tiborsiz qoldiradi | StatefulShellRoute yoki har bir tab uchun alohida Navigator kerakligini tushuntiradi |
Tez-tez so'raladigan intervyu savollari#
Savol: Navigator 1.0'dan Navigator 2.0'ga o'tishning asosiy sababi nima?
Javob: Navigator 1.0 imperativ (push/pop buyruqlari) va sahifalar
stekini "holat" sifatida tasvirlay olmaydi — bu deep linking, veb URL
sinxronizatsiyasi va murakkab orqaga tugma boshqaruvini qiyinlashtiradi.
Navigator 2.0 stekni Page obyektlari ro'yxati sifatida deklarativ
tasvirlashga imkon beradi — dastur "hozir stek qanday ko'rinishi kerak"ni
aytadi, Flutter esa buni amalga oshirish uchun kerakli animatsiyalangan
push/pop amallarini o'zi hisoblaydi.
Savol: go_router'da context.go() va context.push() orasidagi farq
nima?
Javob: context.go() joriy stekni yangi manzil bilan almashtiradi —
orqaga qaytish avvalgi sahifaga emas, go'dan oldingi holatga qarab
o'zgaradi (odatda oxirgi "go qilingan" manzilga). context.push() esa
yangi sahifani mavjud stekning ustiga qo'shadi — orqaga tugmasi aniq
oldingi sahifaga qaytaradi. Odatda tab almashtirish yoki root navigatsiya
uchun go, detallar sahifasiga (masalan, ro'yxatdan element ochish) uchun
push ishlatiladi.
Savol: Deep link orqali kelgan foydalanuvchi, agar u autentifikatsiya qilinmagan bo'lsa, qanday to'g'ri boshqariladi?
Javob: GoRouter'ning redirect callback'i orqali — har bir
navigatsiyada (deep link orqali kelgan bo'lsa ham) autentifikatsiya holati
tekshiriladi. Agar foydalanuvchi tizimga kirmagan bo'lsa, redirect
/login'ga yo'naltiradi, lekin asl maqsad manzilini (masalan, query
parametr yoki holat orqali) saqlab qo'yish kerak, shunda muvaffaqiyatli
login'dan keyin foydalanuvchi dastlab ochmoqchi bo'lgan sahifaga
yo'naltiriladi, home ekraniga emas.
Savol: StatefulShellRoute qanday muammoni hal qiladi?
Javob: Oddiy IndexedStack yoki oddiy tab almashtirish har bir tab
ichidagi navigatsiya stekini saqlamaydi — foydalanuvchi "Katalog" tabida
chuqur ekranga o'tib, "Profil"ga o'tib qaytsa, "Katalog" boshidan
boshlanadi. StatefulShellRoute har bir tab uchun mustaqil Navigator
stekini saqlaydi, shunda har bir tab o'z navigatsiya holatini eslab qoladi —
bu native ilovalarning odatiy bottom-tab xatti-harakati bilan mos keladi.
Savol: Nega extra parametri orqali murakkab obyekt uzatish ba'zan
xato strategiya bo'lishi mumkin?
Javob: extra faqat Dart runtime xotirasida uzatiladi — u URL'ning bir
qismi emas. Agar foydalanuvchi shu sahifaga to'g'ridan-to'g'ri URL orqali
(deep link, veb'da sahifani yangilash) kirsa, extra null bo'ladi va
ilova qulab tushishi yoki bo'sh ekran ko'rsatishi mumkin. To'g'ri strategiya
— faqat ID'ni URL orqali uzatish va ekranning o'zi kerakli ma'lumotni
(masalan, keshdan yoki tarmoqdan) ID asosida yuklay olishini ta'minlash.
Savol: Android'da orqaga tugmasi bosilganda ilova kutilmagan tarzda yopilib ketadi — bu qanday sabab bo'lishi mumkin?
Javob: Bu odatda Navigator steki kutilganidan qisqaroq bo'lganida
yuz beradi — masalan, deep link orqali to'g'ridan-to'g'ri ichki sahifaga
tushilgan, lekin oldingi (root) sahifalar stekka qo'shilmagan. Sog'lom
odat — deep link handler root sahifani ham stekka qo'shib, keyin maqsad
sahifani ustiga qo'yishi kerak, shunda orqaga tugmasi ilovadan chiqish
o'rniga mantiqiy oldingi ekranga qaytaradi.
Savol: go_router'da path parametrlari (:id) va query
parametrlari orasida qachon qaysi birini tanlaysiz?
Javob: Path parametr — resursning identifikatori bo'lgan, o'sha
manzilning ajralmas qismi bo'lgan qiymatlar uchun (/product/42 — 42'siz
bu manzil ma'nosiz). Query parametr — ixtiyoriy, filtrlash/qidiruv kabi
manzilni "o'zgartiruvchi" qiymatlar uchun (/search?q=flutter&sort=price).
Bu farq REST API konventsiyalari bilan mos keladi va URL'larni tushunarli,
bookmark qilinadigan qiladi.
Savol: Veb uchun Flutter ilovasida go_router brauzer tarixi
(orqaga/oldinga tugmalari) bilan qanday ishlaydi, va bu Navigator 1.0'da
nega ishlamas edi?
Javob: go_router RouteInformationParser/RouteInformationProvider
orqali brauzerning URL satri va tarix stekini to'g'ridan-to'g'ri
GoRouter'ning ichki holatiga bog'laydi — brauzerda orqaga bosish
GoRouter holatini avtomatik mos keladigan oldingi sahifaga o'zgartiradi.
Navigator 1.0'da bunday tabiiy bog'lanish yo'q edi, chunki uning steki
faqat dastur ichidagi buyruqlar (push/pop) orqali boshqarilardi va
brauzer URL'i bilan hech qanday rasmiy aloqasi yo'q edi.
Autentifikatsiya bilan himoyalangan deep link oqimini qur
Amaliy topshiriq: go_router bilan uchta sahifadan (/, /login,
/product/:id) iborat kichik navigatsiya sxemasi quring. redirect
mantig'ini shunday yozingki, agar tizimga kirmagan foydalanuvchi
to'g'ridan-to'g'ri /product/42'ni ochsa, avval /login'ga
yo'naltirilsin, muvaffaqiyatli login'dan keyin esa aynan /product/42'ga
qaytarilsin (home ekraniga emas).
Maslahatni ko'rsatish
go_router'da redirect callback yozing: agar foydalanuvchi tizimga kirmagan bo'lsa /login'ga yo'naltiring, lekin asl manzilni saqlab, muvaffaqiyatli logindan keyin o'sha manzilga qaytaring.