Azimjon Abdurasulov

06. Dynamic Actions, Forms va State

⚡ Server-Driven UI (SDUI) — Mukammal Enterprise & System Design

06. Dinamik Harakatlar (Actions), Input Formalar va Holat Boshqaruvi (State Management)

"Dinamik matn va rasmlarni ko'rsatish — bu oson. Ammo serverdan kelgan dinamik formani (kredit arizasi, pasport ma'lumotlari) mijoz kiritishi, uning validatsiyasi va xavfsiz jo'natilishi haqiqiy Senior mahoratini talab qiladi."


1. Passiv SDUI vs Interaktiv SDUI

Dastlabki SDUI tizimlari (masalan, yangiliklar tasmasi) faqat o'qish (Read-Only) uchun mo'ljallangan edi. Zamonaviy FinTech va E-Commerce ilovalarda esa interaktiv formalar talab qilinadi:

  • Foydalanuvchi kiritadigan matn maydonlari (text_input, secure_input).
  • Tanlov elementlari (dropdown, radio_group, checkbox).
  • Kommutatorlar (toggle_switch).

Oltin Qoida: Network Latency vs UX

Agar foydalanuvchi har bir harfni yozganda serverga "shu to'g'rimi?" deb so'rov yuborilsa, harflar kechikib (lag) paydo bo'ladi. Xulosa: Validatsiya qoidalari serverdan keladi, biroq validatsiya tekshiruvi va oraliq holat (State) 100% klient xotirasida (0 ms) bajariladi.

Quyidagi diagrammada ikki tomonlama bog'lanish (Two-Way Binding) zanjiri tasvirlangan:

flowchart LR
    ServerSchema["Server JSON Schema\n(Fields & Validation Rules)"] --> FormStore["Client FormStateStore\n(In-Memory Values & Errors)"]
    FormStore <-->|"Two-Way Binding"| SwiftUIView["SwiftUI Dynamic Form\n(TextField, Toggle, Dropdown)"]
    
    UserAction["Foydalanuvchi 'Yuborish'ni bosdi"] --> LocalValidator["Local Validation Engine\n(Regex, Required, Length)"]
    LocalValidator -->|"Xatolik bo'lsa"| ErrorDisplay["Qizil Ogohlantirishlar (0 ms)"]
    LocalValidator -->|"Hammasi to'g'ri"| NetworkSubmit["POST /api/v1/submit\n(JSON Payload)"]
    NetworkSubmit --> ServerResponse["Server Yangi Ekranni Qaytaradi"]

    style FormStore fill:#131720,stroke:#bef264,stroke-width:2px
    style LocalValidator fill:#181d26,stroke:#7da2ce,stroke-width:1px
    style SwiftUIView fill:#181d26,stroke:#60a5fa,stroke-width:1px

2. Serverdan Keladigan Dinamik Validatsiya Qoidalari

Server maydon bilan birga uning qoidalarini ham JSON da taqdim etadi:

{
  "id": "phone_input_field",
  "type": "text_input",
  "props": {
    "label": "Telefon raqamingiz",
    "placeholder": "+998 90 123 45 67",
    "keyboardType": "phonePad",
    "stateKey": "user_phone"
  },
  "validations": [
    {
      "rule": "required",
      "errorMessage": "Telefon raqami kiritilishi shart"
    },
    {
      "rule": "regex",
      "pattern": "^\\+998[0-9]{9}$",
      "errorMessage": "Format: +998XXXXXXXXX ko'rinishida bo'lsin"
    }
  ]
}

3. Klientdagi Holat Markazi (FormStateStore)

Foydalanuvchi kiritgan barcha maydon qiymatlarini bitta xavfsiz xotirada saqlovchi va validatsiyani nazorat qiluvchi reaktiv sinf:

import SwiftUI

@MainActor
@Observable
public final class FormStateStore {
    // Har bir maydonning joriy matni: ["user_phone": "+998901234567"]
    public var values: [String: String] = [:]
    
    // Xatoliklar xaritasi: ["user_phone": "Format xato"]
    public var errors: [String: String] = [:]
    
    // Forma yuborilish holati
    public var isSubmitting: Bool = false
    
    public init() {}
    
    public func setValue(_ value: String, for key: String) {
        values[key] = value
        // Qiymat o'zgarganda xatolikni tozalaymiz
        errors.removeValue(forKey: key)
    }
    
    public func validate(key: String, rules: [ValidationRule]) -> Bool {
        let text = values[key] ?? ""
        
        for rule in rules {
            switch rule.type {
            case .required:
                if text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
                    errors[key] = rule.errorMessage
                    return false
                }
            case .regex(let pattern):
                let predicate = NSPredicate(format: "SELF MATCHES %@", pattern)
                if !predicate.evaluate(with: text) {
                    errors[key] = rule.errorMessage
                    return false
                }
            case .minLength(let min):
                if text.count < min {
                    errors[key] = rule.errorMessage
                    return false
                }
            }
        }
        
        errors.removeValue(forKey: key)
        return true
    }
    
    /// PCI-DSS Xavfsizlik talabi: Maxfiy ma'lumotlarni tozalash
    public func clearSensitiveData() {
        values.removeAll()
        errors.removeAll()
        isSubmitting = false
    }
}

public struct ValidationRule: Sendable {
    public enum RuleType: Sendable {
        case required
        case regex(pattern: String)
        case minLength(count: Int)
    }
    
    public let type: RuleType
    public let errorMessage: String
}

4. Dinamik TextField Komponenti

SwiftUI da ushbu holat bilan ikki tomonlama bog'langan (two-way binding) matn kiritish komponenti:

import SwiftUI

public struct SDUITextFieldView: View {
    public let label: String
    public let placeholder: String
    public let stateKey: String
    public let rules: [ValidationRule]
    @Bindable public var formStore: FormStateStore
    
    public var body: some View {
        VStack(alignment: .leading, spacing: 6) {
            Text(label)
                .font(.caption.bold())
                .foregroundColor(.secondary)
            
            TextField(
                placeholder,
                text: Binding(
                    get: { formStore.values[stateKey] ?? "" },
                    set: { formStore.setValue($0, for: stateKey) }
                )
            )
            .textFieldStyle(.plain)
            .padding(12)
            .background(Color(uiColor: .secondarySystemBackground))
            .cornerRadius(8)
            .overlay(
                RoundedRectangle(cornerRadius: 8)
                    .stroke(formStore.errors[stateKey] != nil ? Color.red : Color.clear, lineWidth: 1)
            )
            
            // Xatolik xabari
            if let error = formStore.errors[stateKey] {
                Text(error)
                    .font(.caption2)
                    .foregroundColor(.red)
                    .transition(.opacity)
            }
        }
        .padding(.horizontal, 16)
        .padding(.vertical, 8)
    }
}

5. Dinamik Submit Oqimi (Form Submission Workflow)

Foydalanuvchi "Arizani topshirish" tugmasini bosganda ketma-ketlik:

sequenceDiagram
    autonumber
    participant User as Foydalanuvchi
    participant UI as Dynamic Form (SwiftUI)
    participant Store as FormStateStore
    participant API as Backend API

    User->>UI: "Ariza topshirish" tugmasini bosadi
    UI->>Store: Barcha maydonlarni tekshirish (Local Validation)
    
    alt Xatolik aniqlansa
        Store-->>UI: errors xaritasi to'ldiriladi
        UI-->>User: Qizil xatoliklar bir zumda chiqadi (0 ms)
    else Barcha maydonlar to'g'ri
        UI->>Store: isSubmitting = true (Spinner yoqiladi)
        UI->>API: POST /api/v1/loan/apply { values }
        API-->>UI: 200 OK + Yangi Muvaffaqiyat Ekrani (SDUI)
        UI->>Store: clearSensitiveData() (Xotira tozalanadi)
        UI-->>User: Muvaffaqiyat ekrani ko'rsatiladi!
    end

6. Interaktiv Savol-Javob (Self-Check Quiz)

<details> <summary><b>1-Savol: Nega maxfiy ma'lumotlar (masalan, karta CVV kodi yoki pasport seriyasi) kiritilayotganda FormStateStore'ni diskka yozish (persistence) qat'iyan taqiqlanadi?</b> (Yechimni ko'rish uchun bosing)</summary>

Tahlil va Yechim: Xavfsizlik va xalqaro PCI-DSS standartlari talabi tufayli. FormStateStore faqat operativ xotirada (RAM) saqlanishi kerak. Agar ilova backgroundga o'tsa yoki forma muvaffaqiyatli jo'natilsa, maxfiy kalitlar xotiradan butunlay tozalanishi (formStore.clearSensitiveData()) shart. Diskka yozilsa, qurilma zaxira nusxasi (iTunes/iCloud Backup) orqali ma'lumotlar oshkor bo'lishi mumkin. </details>

<details> <summary><b>2-Savol: Foydalanuvchi forma to'ldirib bo'lgach, internet o'chib qolsa, SDUI qanday harakat qilishi lozim?</b> (Yechimni ko'rish uchun bosing)</summary>

Tahlil va Yechim: Tugma ustidagi yuklanish indikatori (Loading Spinner) to'xtatilib, "Internet aloqasi yo'q. Qaytadan urinib ko'ring" degan alert yoki snackbar ko'rsatilishi kerak. Kiritilgan ma'lumotlar esa yo'qolib ketmasdan maydonlarda saqlanib turishi shart. </details>


7. Amaliy Topshiriq (Senior Challenge)

FormStateStore ga yangi dependent_validation qoidasini qo'shing:

  • Shart: Agar delivery_type dropdown maydoni "courier" ga teng bo'lsa, address_input maydoni majburiy (required) bo'lsin. Agar "pickup" (o'zi olib ketish) bo'lsa, manzil maydoni ixtiyoriy bo'lsin.
  • Ushbu dinamik bog'liqlikni klientda 0 ms kechikish bilan ishlovchi reaktiv mantiq orqali amalga oshiring!

Markdown manba

iOS Lessons