05. Dynamic Navigation va Routing Tizimi
⚡ Server-Driven UI (SDUI) — Mukammal Enterprise & System Design
05. Server-Driven Navigation va Dynamic Routing Tizimi
"Faqat UI ko'rinishini serverdan boshqarish — bu yarim yo'l. Haqiqiy qudrat foydalanuvchi qaysi ekranga, qanday o'tish usuli bilan (push, modal, sheet) o'tishi va back-stack qanday boshqarilishini server hal qilganda namoyon bo'ladi."
1. Nega Hardcoded NavigationLink SDUI da Ishlamaydi?
Klassik SwiftUI ilovasida tugma bosilganda qayerga o'tish kodga mixlab qo'yiladi:
// ❌ HARDCODED NAVIGATION: Relizsiz o'zgartirib bo'lmaydi
NavigationLink(destination: CheckoutViewController()) {
Text("Sotib olish")
}
Agar kompaniya to'lov oqimini o'zgartirmoqchi bo'lsa (masalan, to'g'ridan-to'g'ri to'lovga emas, avval "Aksiya shartlari" degan yangi ekranga yo'naltirish), an'anaviy ilovada buni relizsiz qilib bo'lmaydi.
Server-Driven Navigation Qanday Ishlaydi?
Tugmaga faqat abstrakt Action biriktiriladi. Server tugma bosilganda nima sodir bo'lishini deklarativ JSON orqali belgilaydi:
{
"event": "onTap",
"type": "navigate",
"route": "app://checkout/summary?orderId=8841",
"presentation": "sheet",
"sheetDetents": ["medium", "large"],
"clearStackOnComplete": false
}
2. Dynamic Routing Arxitekturasi (Coordinator Pattern)
Navigatsiyani markazlashgan holda boshqarish uchun Universal Router & Coordinator arxitekturasi qo'llaniladi:
flowchart TD
UserTap["Foydalanuvchi Tugmani Bosdi"] --> ActionDispatcher["ActionDispatcher.dispatch(action)"]
ActionDispatcher --> RouteClassifier{"Route Turini Aniqlash"}
RouteClassifier -->|"app://sdui/screen?path=..."| SDUILoader["Dinamik SDUI Ekranni Yuklash"]
RouteClassifier -->|"app://native/camera"| NativeBridge["Native Funksiyani Ishga Tushirish (FaceID, Kamera)"]
RouteClassifier -->|"https://azimjondev.uz/help"| WebSafari["Safari / WebKit Brauzerda Ochish"]
RouteClassifier -->|"app://navigation/pop_to_root"| StackCleaner["Navigation Stack'ni Tozalash"]
SDUILoader --> PresentationEngine{"Taqdimot Turi (Presentation)"}
PresentationEngine -->|"push"| NavStack["NavigationStack (Push)"]
PresentationEngine -->|"sheet"| SheetModal["BottomSheet / Sheet"]
PresentationEngine -->|"fullScreen"| FullModal["FullScreenCover"]
style ActionDispatcher fill:#131720,stroke:#bef264,stroke-width:2px
style RouteClassifier fill:#181d26,stroke:#7da2ce,stroke-width:1px
style PresentationEngine fill:#181d26,stroke:#60a5fa,stroke-width:1px
3. Multi-Step Dynamic Wizard va Back-Stack Boshqaruvi
Keling, real FinTech o'tkazma ssenariysini ko'rib chiqamiz:
stateDiagram-v2
direction LR
[*] --> Step1: 1. Karta Raqami Kiritish
Step1 --> Step2: 2. Summani Kiritish
Step2 --> Step3: 3. SMS OTP Tasdiqlash
Step3 --> Success: 4. Muvaffaqiyat Ekrani (Success)
Success --> [*]: "Bosh Sahifaga Qaytish"\n(action: pop_to_root)
Foydalanuvchi muvaffaqiyat ekranida "Bosh sahifaga qaytish" tugmasini bosganida, server quyidagi action'ni beradi:
{
"type": "navigation_command",
"command": "pop_to_root"
}
Clientdagi router.popToRoot() komandasi bir zumda oradagi barcha 3 ta ekranni xotiradan tozalab, foydalanuvchini asosiy sahifaga qaytaradi. Foydalanuvchi "Orqaga" bosib yana SMS kod kiritish oynasiga qaytib qolmaydi!
4. Swift 6 da To'liq Ishlovchi Universal Router Kodi
import SwiftUI
// MARK: - 1. Taqdimot Turlari
public enum PresentationStyle: String, Decodable, Sendable {
case push
case sheet
case fullScreenCover
}
// MARK: - 2. Navigatsiya Marshruti
public struct SDUIAppRoute: Hashable, Identifiable, Sendable {
public var id: String { url.absoluteString + "_\(presentation.rawValue)" }
public let url: URL
public let presentation: PresentationStyle
}
// MARK: - 3. Markaziy Router
@MainActor
@Observable
public final class SDUIRouter {
public static let shared = SDUIRouter()
public var navigationPath = NavigationPath()
public var sheetRoute: SDUIAppRoute?
public var fullScreenRoute: SDUIAppRoute?
private init() {}
/// Serverdan kelgan marshrutni bajarish
public func navigate(to rawRoute: String, presentation: PresentationStyle = .push) {
guard let url = URL(string: rawRoute) else { return }
// Maxsus navigatsiya buyruqlari
if url.scheme == "app" && url.host == "navigation" {
handleNavigationCommand(url: url)
return
}
let appRoute = SDUIAppRoute(url: url, presentation: presentation)
switch presentation {
case .push:
navigationPath.append(appRoute)
case .sheet:
sheetRoute = appRoute
case .fullScreenCover:
fullScreenRoute = appRoute
}
}
private func handleNavigationCommand(url: URL) {
switch url.path {
case "/pop_to_root":
popToRoot()
case "/pop":
pop()
case "/dismiss":
sheetRoute = nil
fullScreenRoute = nil
default:
break
}
}
public func pop() {
if !navigationPath.isEmpty {
navigationPath.removeLast()
}
}
public func popToRoot() {
navigationPath = NavigationPath()
sheetRoute = nil
fullScreenRoute = nil
}
}
5. Asosiy Ilova Konteyneri (SDUIRootContainerView)
Router'ni butun ilovaga ulovchi asosiy SwiftUI ko'rinishi:
import SwiftUI
public struct SDUIRootContainerView: View {
@State private var router = SDUIRouter.shared
public let rootEndpoint: String
public init(rootEndpoint: String) {
self.rootEndpoint = rootEndpoint
}
public var body: some View {
NavigationStack(path: $router.navigationPath) {
RemoteSDUIScreen(endpoint: rootEndpoint)
.navigationDestination(for: SDUIAppRoute.self) { route in
RemoteSDUIScreen(endpoint: route.url.path)
}
}
.sheet(item: $router.sheetRoute) { route in
RemoteSDUIScreen(endpoint: route.url.path)
}
.fullScreenCover(item: $router.fullScreenRoute) { route in
RemoteSDUIScreen(endpoint: route.url.path)
}
}
}
public struct RemoteSDUIScreen: View {
public let endpoint: String
@State private var components: [ComponentNode] = []
public var body: some View {
SDUIScreenView(components: components)
.task {
// Serverdan endpoint bo'yicha JSON yuklash
}
}
}
6. Interaktiv Savol-Javob (Self-Check Quiz)
<details> <summary><b>1-Savol: Nega SDUI navigatsiyasida har doim Universal Deeplink formati (masalan, 'app://profile/edit') ishlatilishi shart?</b> (Yechimni ko'rish uchun bosing)</summary>
Tahlil va Yechim: Chunki bitta yagona format 3 ta turli manba uchun bir xil xizmat qiladi:
- Ichki SDUI tugmalari: Barcha komponentlar ushbu havola orqali harakat qiladi.
- Push-Notification: Foydalanuvchiga yuborilgan push xabari to'g'ridan-to'g'ri kerakli ekranni ochadi.
- Universal Links: Veb-saytdagi havolani bosganda ilova bir xil marshrut bilan kerakli ekranga olib o'tadi.
</details>
<details> <summary><b>2-Savol: Xavfsizlik bo'yicha: agar server JSON orqali 'app://safari?url=javascript:...' yuborsa, router qanday himoyalanishi lozim?</b> (Yechimni ko'rish uchun bosing)</summary>
Tahlil va Yechim: Router ichida URL Scheme Whitelist (Ruxsat etilgan sxemalar ro'yxati) bo'lishi shart. Faqat tasdiqlangan sxemalar (app://, https://) va xavfsiz ichki domenlar ochiladi. Har qanday javascript:, file:// yoki ruxsat etilmagan sxemalar darhol bloklanadi va xavfsizlik loglariga qayd etiladi. </details>
7. Amaliy Topshiriq (Senior Challenge)
SDUIRouter ga sheet orqali ochilgan modal oyna yopilganda o'zidan oldingi ekranni yangilash (Refresh Trigger) parametrini qo'shing:
{
"type": "navigate",
"route": "app://card/add",
"presentation": "sheet",
"onDismiss": { "action": "reload_screen" }
}
Muvaffaqiyatli karta qo'shilgach, sheet yopilib, asosiy sahifadagi kartalar ro'yxati qayta so'rovsiz yoki yangilangan holatda chizilishini ta'minlang!