# Flutter 10: Professional REST API — Dio, Interceptors, Token Refresh va Clean Repository

> **Holat:** 2026-yil. Zamonaviy Flutter ilovalarida eng muhim ko'nikmalardan biri — bu masofaviy server (REST API) bilan barqaror, xavfsiz va uzluksiz ishlay olishdir. Tarmoq yo'qolishi, sekin internet, 401 Unauthorized holatlari va JSON parslashdagi xatolar ilovani buzmasligi kerak. Ushbu darsda Clean Architecture asosida Dio mijozi, avtomatik JWT Token Refresh interceptorlari, tiplangan modellar va oflayn keshlash mexanizmini bosqichma-bosqich quramiz.

---

## Mundarija

1. [Tarmoq arxitekturasi: Clean Architecture qatlamlari](#clean-architecture)
2. [Dio vs http: Nega Dio standart hisoblanadi?](#dio-vs-http)
3. [Result Pattern: Xatoliklarni xavfsiz boshqarish](#result-pattern)
4. [Dio instansiyasi va BaseOptions sozlamalari](#dio-setup)
5. [JWT Auth & Refresh Token Interceptor (Eng muhim mexanizm)](#refresh-token)
6. [Type-safe Model va JSON Serialization](#json-models)
7. [Repository va DataSource qatlami](#repository-pattern)
8. [Riverpod 3 bilan UI ga ulash va holatni boshqarish](#ui-integration)
9. [Oflayn kesh: SharedPreferences va Hive bilan uzluksizlik](#offline-cache)
10. [Xulosa va Xavfsizlik qoidalari](#xulosa)
11. [Rasmiy Manbalar](#manbalar)

---

## 1. Tarmoq arxitekturasi: Clean Architecture qatlamlari {#clean-architecture}

To'g'ridan-to'g'ri `Widget` ichida `http.get()` chaqirish — bu loyihani o'limga olib boruvchi yo'ldir. Clean Architecture tizimida tarmoq oqimi quyidagicha tashkil etiladi:

```
┌────────────────────────────────────────────────────────────────────────┐
│                          PRESENTATION LAYER                            │
│  UI Widgets (Screens)  ◄───►  State Notifiers (Riverpod / BLoC)        │
└───────────────────────────────────┬────────────────────────────────────┘
                                    │ Chaqiradi (Uses)
┌───────────────────────────────────▼────────────────────────────────────┐
│                             DOMAIN LAYER                               │
│  Entities (Pok Dart obyektlari)  ◄──►  Repository Interface (Shartnoma)│
│  Failures / Results (Xatoliklar klassi)                                │
└───────────────────────────────────▲────────────────────────────────────┘
                                    │ Amalga oshiradi (Implements)
┌───────────────────────────────────┴────────────────────────────────────┐
│                              DATA LAYER                                │
│  Repositories Impl  ──►  RemoteDataSource (Dio API Client)             │
│                     ──►  LocalDataSource (Kesh / Hive / SQLite)        │
│  DTOs / Models (fromJson / toJson)                                     │
└────────────────────────────────────────────────────────────────────────┘
```

**Asosiy qoida:** Domain qatlami hech qanday tashqi paketga (hatto Flutter'ga ham) bog'liq bo'lmasligi kerak — sof Dart kodidan iborat bo'ladi.

---

## 2. Dio vs http: Nega Dio standart hisoblanadi? {#dio-vs-http}

Standart `http` paketi juda yengil, ammo korporativ ilovalar uchun yetarli emas:

| Xususiyat | `http` paketi | `dio` paketi (^5.8+) |
| :--- | :--- | :--- |
| **Interceptors (Filtrlar)** | Yo'q (qo'lda o'rab chiqish kerak) | O'rnatilgan (Request, Response, Error) |
| **Global Base URL & Headers** | Har bir so'rovda qo'lda uzatiladi | `BaseOptions` orqali markaziy o'rnatiladi |
| **Avtomatik JSON parslash** | `jsonDecode(response.body)` shart | Dio `response.data` da avtomatik `Map` qaytaradi |
| **Fayl yuklash / yuklab olish** | Murakkab `MultipartRequest` | Progress tinglovchi (`onSendProgress`) bilan oson |
| **So'rovni bekor qilish** | Murakkab | `CancelToken` orqali bir lahzada bekor qilinadi |
| **Timeout nazorati** | Qiyin | `connectTimeout`, `receiveTimeout` mavjud |

Kerakli paketlarni `pubspec.yaml` ga qo'shamiz:
```yaml
dependencies:
  flutter:
    sdk: flutter
  dio: ^5.8.0+1
  flutter_riverpod: ^2.6.1
  shared_preferences: ^2.5.2
```

---

## 3. Result Pattern: Xatoliklarni xavfsiz boshqarish {#result-pattern}

Dart'da doimiy `try-catch` yozish o'rniga, natijani **`Result<T>`** muhrlangan klassi (Sealed Class) orqali ifodalash xavfsizdir:

```dart
sealed class Result<T> {
  const Result();
}

final class Success<T> extends Result<T> {
  final T data;
  const Success(this.data);
}

final class Failure<T> extends Result<T> {
  final String message;
  final int? statusCode;
  const Failure(this.message, {this.statusCode});
}
```

Bu pattern yordamida UI qismida `switch` orqali barcha holatlarni compile-time xavfsizlik bilan tekshirish mumkin:
```dart
switch (result) {
  case Success(:final data):
    showData(data);
  case Failure(:final message):
    showErrorSnackbar(message);
}
```

---

## 4. Dio instansiyasi va BaseOptions sozlamalari {#dio-setup}

Barcha API so'rovlari uchun yagona `ApiClient` sinfini yaratamiz:

```dart
import 'package:dio/dio.dart';

class ApiClient {
  static const String baseUrl = 'https://api.azimjondev.uz/v1';

  late final Dio dio;

  ApiClient({required String? initialToken}) {
    dio = Dio(
      BaseOptions(
        baseUrl: baseUrl,
        connectTimeout: const Duration(seconds: 15),
        receiveTimeout: const Duration(seconds: 15),
        sendTimeout: const Duration(seconds: 15),
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
          if (initialToken != null) 'Authorization': 'Bearer $initialToken',
        },
      ),
    );

    // Logging interceptor (Faqat debug paytida)
    dio.interceptors.add(
      LogInterceptor(
        requestHeader: false,
        requestBody: true,
        responseBody: true,
        responseHeader: false,
        error: true,
      ),
    );
  }
}
```

---

## 5. JWT Auth & Refresh Token Interceptor (Eng muhim mexanizm) {#refresh-token}

Foydalanuvchi ilovadan foydalanayotganda 15 daqiqalik Access Token muddati tugaydi va server `401 Unauthorized` xatosini qaytaradi.
**To'g'ri yechim:** Foydalanuvchini login sahifasiga chiqarib yubormasdan, orqa fonda Refresh Token orqali yangi Access Token olish va to'xtab qolgan so'rovni avtomatik qayta yuborish (Retry):

```dart
import 'package:dio/dio.dart';

class AuthInterceptor extends QueuedInterceptor {
  final Dio dio;
  final Future<String?> Function() getAccessToken;
  final Future<String?> Function() getRefreshToken;
  final Future<void> Function(String newAccess, String newRefresh) onTokensRefreshed;
  final VoidCallback onSessionExpired;

  AuthInterceptor({
    required this.dio,
    required this.getAccessToken,
    required this.getRefreshToken,
    required this.onTokensRefreshed,
    required this.onSessionExpired,
  });

  // 1. Har bir so'rovga oxirgi yangi tokenni qo'shamiz
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
    final token = await getAccessToken();
    if (token != null && !options.headers.containsKey('Authorization')) {
      options.headers['Authorization'] = 'Bearer $token';
    }
    handler.next(options);
  }

  // 2. 401 xatosi yuz berganda avtomatik refresh qilamiz
  @override
  void onError(DioException err, ErrorInterceptorHandler handler) async {
    if (err.response?.statusCode == 401) {
      final refreshToken = await getRefreshToken();

      if (refreshToken == null) {
        onSessionExpired();
        return handler.reject(err);
      }

      try {
        // Asosiy dio emas, alohida toza dio orqali so'raymiz (tsiklni oldini olish uchun)
        final refreshDio = Dio(BaseOptions(baseUrl: dio.options.baseUrl));
        final response = await refreshDio.post('/auth/refresh', data: {
          'refresh_token': refreshToken,
        });

        if (response.statusCode == 200) {
          final newAccessToken = response.data['access_token'] as String;
          final newRefreshToken = response.data['refresh_token'] as String;

          // Yangi tokenlarni mahalliy xotiraga saqlaymiz
          await onTokensRefreshed(newAccessToken, newRefreshToken);

          // Xatoga uchragan so'rovning headerini yangilab, qayta yuboramiz
          final requestOptions = err.requestOptions;
          requestOptions.headers['Authorization'] = 'Bearer $newAccessToken';

          final retryResponse = await dio.fetch(requestOptions);
          return handler.resolve(retryResponse);
        }
      } catch (refreshError) {
        // Refresh token ham eskirgan bo'lsa, foydalanuvchini logindan chiqaramiz
        onSessionExpired();
        return handler.reject(err);
      }
    }

    // Boshqa turdagi xatoliklarni davom ettiramiz
    handler.next(err);
  }
}
```

*E'tibor bering:* `QueuedInterceptor` dan foydalanildi. Bu bir vaqtning o'zida bir nechta so'rov 401 olganda serverga bir nechta refresh so'rov ketishining oldini oladi va navbat bilan hal qiladi!

---

## 6. Type-safe Model va JSON Serialization {#json-models}

Serverdan keladigan ma'lumotlar uchun mustahkam model tuzamiz:

```dart
class ProductModel {
  final int id;
  final String title;
  final double price;
  final String description;
  final String imageUrl;

  const ProductModel({
    required this.id,
    required this.title,
    required this.price,
    required this.description,
    required this.imageUrl,
  });

  factory ProductModel.fromJson(Map<String, dynamic> json) {
    return ProductModel(
      id: json['id'] as int,
      title: json['title'] as String? ?? 'Nomsiz mahsulot',
      price: (json['price'] as num?)?.toDouble() ?? 0.0,
      description: json['description'] as String? ?? '',
      imageUrl: json['image_url'] as String? ?? 'https://via.placeholder.com/150',
    );
  }

  Map<String, dynamic> toJson() => {
    'id': id,
    'title': title,
    'price': price,
    'description': description,
    'image_url': imageUrl,
  };
}
```

---

## 7. Repository va DataSource qatlami {#repository-pattern}

Clean Architecture qoidasiga binoan, avval interfeys yoziladi:

```dart
// Domain Layer: Interface
abstract interface class ProductRepository {
  Future<Result<List<ProductModel>>> getProducts();
  Future<Result<ProductModel>> getProductById(int id);
}

// Data Layer: Implementation
class ProductRepositoryImpl implements ProductRepository {
  final Dio dio;

  const ProductRepositoryImpl(this.dio);

  @override
  Future<Result<List<ProductModel>>> getProducts() async {
    try {
      final response = await dio.get('/products');

      if (response.statusCode == 200) {
        final list = (response.data as List)
            .map((e) => ProductModel.fromJson(e as Map<String, dynamic>))
            .toList();
        return Success(list);
      }

      return Failure('Server xatosi: ${response.statusCode}', statusCode: response.statusCode);
    } on DioException catch (e) {
      return Failure(_handleDioError(e), statusCode: e.response?.statusCode);
    } catch (e) {
      return Failure('Kutilmagan xatolik: $e');
    }
  }

  @override
  Future<Result<ProductModel>> getProductById(int id) async {
    try {
      final response = await dio.get('/products/$id');
      return Success(ProductModel.fromJson(response.data));
    } on DioException catch (e) {
      return Failure(_handleDioError(e));
    }
  }

  String _handleDioError(DioException error) {
    switch (error.type) {
      case DioExceptionType.connectionTimeout:
      case DioExceptionType.sendTimeout:
      case DioExceptionType.receiveTimeout:
        return 'Server bilan aloqa vaqti tugadi (Timeout). Internetni tekshiring.';
      case DioExceptionType.badResponse:
        final code = error.response?.statusCode;
        if (code == 404) return 'Ma\'lumot topilmadi (404).';
        if (code == 500) return 'Serverda ichki xatolik (500).';
        return 'Xato javob: $code';
      case DioExceptionType.connectionError:
        return 'Internet aloqasi mavjud emas.';
      default:
        return 'Tarmoq xatoligi yuz berdi.';
    }
  }
}
```

---

## 8. Riverpod 3 bilan UI ga ulash va holatni boshqarish {#ui-integration}

Repository'ni Riverpod provayderiga joylaymiz va UI uchun `AsyncNotifier` yaratamiz:

```dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 1. Dio va Repository provayderlari
final dioProvider = Provider<Dio>((ref) {
  return ApiClient(initialToken: null).dio;
});

final productRepositoryProvider = Provider<ProductRepository>((ref) {
  return ProductRepositoryImpl(ref.watch(dioProvider));
});

// 2. Mahsulotlar holatini boshqaruvchi FutureProvider
final productsProvider = FutureProvider<List<ProductModel>>((ref) async {
  final repo = ref.watch(productRepositoryProvider);
  final result = await repo.getProducts();

  return switch (result) {
    Success(:final data) => data,
    Failure(:final message) => throw Exception(message),
  };
});
```

### UI qismida ekranga chiqarish:

```dart
class ProductListScreen extends ConsumerWidget {
  const ProductListScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final productsAsync = ref.watch(productsProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Mahsulotlar katalogi'),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: () => ref.refresh(productsProvider),
          ),
        ],
      ),
      body: productsAsync.when(
        loading: () => const Center(child: CircularProgressIndicator.adaptive()),
        error: (err, stack) => Center(
          child: Padding(
            padding: const EdgeInsets.all(24),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Icon(Icons.wifi_off_rounded, size: 64, color: Colors.redAccent),
                const SizedBox(height: 16),
                Text(err.toString().replaceAll('Exception: ', ''), textAlign: TextAlign.center),
                const SizedBox(height: 16),
                ElevatedButton(
                  onPressed: () => ref.refresh(productsProvider),
                  child: const Text('Qayta urinish'),
                ),
              ],
            ),
          ),
        ),
        data: (products) => ListView.builder(
          itemCount: products.length,
          itemBuilder: (context, index) {
            final item = products[index];
            return ListTile(
              leading: ClipRRect(
                borderRadius: BorderRadius.circular(8),
                child: Image.network(item.imageUrl, width: 50, height: 50, fit: BoxFit.cover),
              ),
              title: Text(item.title),
              subtitle: Text('\$${item.price.toStringAsFixed(2)}'),
              trailing: const Icon(Icons.chevron_right),
            );
          },
        ),
      ),
    );
  }
}
```

---

## 9. Oflayn kesh: SharedPreferences va Hive bilan uzluksizlik {#offline-cache}

Tarmoq bo'lmaganda ilova bo'sh ekran ko'rsatmasligi uchun kesh qatlami (LocalDataSource) qo'shiladi:
1. So'rov yuboriladi.
2. Agar tarmoq bor bo'lsa: ma'lumot serverdan olinadi, mahalliy xotiraga saqlanadi va ekranga beriladi.
3. Agar tarmoq yo'q bo'lsa: mahalliy xotiradagi oxirgi ma'lumot olinadi va `Snackbar` orqali "Oflayn rejim" xabarnomasi bilan taqdim etiladi.

---

## 10. Xulosa va Xavfsizlik qoidalari {#xulosa}

1. **Tokenlarni hech qachon ochiq kodda saqlamang:** API sirlari va kalitlar uchun `flutter_dotenv` yoki compile-time `--dart-define` dan foydalaning.
2. **Katta JSON larni Isolate orqali parslash:** 1 MB dan katta bo'lgan JSON ro'yxatlarini asosiy oqimda parslash kadr tushishiga olib keladi. Buning uchun `compute(parseJsonFunction, responseBody)` dan foydalaning.
3. **Cancellation Token:** Agar foydalanuvchi sahifadan chiqib ketsa, `cancelToken.cancel()` orqali behuda yuklanayotgan so'rovni to'xtating.

---

## 11. Rasmiy Manbalar {#manbalar}

- [Flutter Networking & HTTP Guide](https://docs.flutter.dev/cookbook/networking/fetch-data)
- [Dio Official Package on pub.dev](https://pub.dev/packages/dio)
- [Riverpod 3 Architecture Guidelines](https://riverpod.dev)
- [Flutter Concurrency & Isolates](https://docs.flutter.dev/perf/isolates)
