aidoc.uz

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:

  1. 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.
  2. Veb URL bilan sinxronlash qiyin — brauzerning orqaga/oldinga tugmalari va URL satri Navigator stekiga tabiiy ravishda mos kelmaydi.
  3. 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:

RouteInformationParser
URL/deep-link satrini ilova ichidagi tushunarli obyektga aylantiradi
RouterDelegate
Joriy holatga qarab qaysi sahifalar stekini ko'rsatishni belgilaydi (Navigator'ning 'pages' ro'yxatini quradi)
Navigator(pages: [...])
Berilgan Page ro'yxati asosida haqiqiy stekni qurib, farqni animatsiya bilan ko'rsatadi

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'da intent-filter bilan android:autoVerify="true" (App Links uchun) yoki custom scheme ro'yxatga olinadi.
  • iOS: Info.plist'da CFBundleURLTypes (custom scheme) yoki Universal Links uchun apple-app-site-association fayli serverda joylashtiriladi va Associated Domains capability yoqiladi.

5. Sahifalar orasida ma'lumot uzatish#

Uch xil usul bor, va qachon qaysi birini tanlash muhim:

UsulQachon 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/holatJunior darajadagi javobSenior 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 tushadiFaqat Dart kodini tekshiradiAndroid manifest / iOS entitlements sozlamalarini ham tekshiradi
extra orqali uzatilgan obyekt deep link'da null bo'lib chiqadi"Bug" deb hisoblaydiextra faqat runtime xotirada ekanini, URL orqali kelmasligini tushuntiradi va ID-asoslangan qayta yuklashni taklif qiladi
Bottom tab navigatsiyasida tab almashtirilganda ichki stek yo'qoladiMuammoni e'tiborsiz qoldiradiStatefulShellRoute 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.

Amaliy topshiriq

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.