runable
This commit is contained in:
417
docs/API_ARCHITECTURE.md
Normal file
417
docs/API_ARCHITECTURE.md
Normal file
@@ -0,0 +1,417 @@
|
||||
# API Integration Architecture
|
||||
|
||||
## Complete Data Flow Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ PRESENTATION LAYER │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ ProductsPage │ │ CategoriesPage│ │ HomePage │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ └──────────────────┴──────────────────┘ │
|
||||
│ │ │
|
||||
└────────────────────────────┼────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ RIVERPOD PROVIDERS │
|
||||
│ │
|
||||
│ ┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
|
||||
│ │ProductsProvider │ │CategoriesProvider│ │ NetworkProvider │ │
|
||||
│ └────────┬────────┘ └────────┬─────────┘ └────────┬─────────┘ │
|
||||
│ │ │ │ │
|
||||
└───────────┼─────────────────────┼─────────────────────┼────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ USE CASES (Domain) │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │GetAllProducts│ │GetCategories │ │SearchProducts│ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
└─────────┼──────────────────┼──────────────────┼────────────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ REPOSITORIES (Data) │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ ProductRepositoryImpl │ │
|
||||
│ │ │ │
|
||||
│ │ - Offline-first logic │ │
|
||||
│ │ - Exception → Failure conversion │ │
|
||||
│ │ - Cache + Remote coordination │ │
|
||||
│ └──────────────────────┬──────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────────┴───────────────┐ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Local Source │ │ Remote Source│ │
|
||||
│ │ (Hive) │ │ (API) │ │
|
||||
│ └──────────────┘ └──────┬───────┘ │
|
||||
└─────────────────────────────────────────┼──────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ NETWORK LAYER (Core) │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||||
│ │ DIO CLIENT │ │
|
||||
│ │ │ │
|
||||
│ │ ┌────────────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ INTERCEPTORS │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ 1. Logging Interceptor │ │ │
|
||||
│ │ │ → Log requests/responses │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ 2. Auth Interceptor │ │ │
|
||||
│ │ │ → Add auth headers │ │ │
|
||||
│ │ │ → Handle 401 errors │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ 3. Error Interceptor │ │ │
|
||||
│ │ │ → Map status codes to exceptions │ │ │
|
||||
│ │ │ → Extract error messages │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ 4. Retry Interceptor │ │ │
|
||||
│ │ │ → Retry on timeout/connection errors │ │ │
|
||||
│ │ │ → Exponential backoff │ │ │
|
||||
│ │ └────────────────────────────────────────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ HTTP Methods: GET, POST, PUT, DELETE, PATCH │ │
|
||||
│ └──────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||||
│ │ NETWORK INFO │ │
|
||||
│ │ │ │
|
||||
│ │ - Check connectivity status │ │
|
||||
│ │ - Monitor connectivity changes │ │
|
||||
│ │ - Detect connection type (WiFi/Mobile) │ │
|
||||
│ └──────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ BACKEND API │
|
||||
│ │
|
||||
│ GET /api/v1/products │
|
||||
│ GET /api/v1/products/:id │
|
||||
│ GET /api/v1/products/category/:categoryId │
|
||||
│ GET /api/v1/products/search?q=query │
|
||||
│ POST /api/v1/products/sync │
|
||||
│ │
|
||||
│ GET /api/v1/categories │
|
||||
│ GET /api/v1/categories/:id │
|
||||
│ POST /api/v1/categories/sync │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling Flow
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ API Request │
|
||||
└──────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ Network Check │
|
||||
│ (NetworkInfo) │
|
||||
└──────┬───────────┘
|
||||
│
|
||||
├─── No Connection ────────────────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────┐ ┌────────────────────┐
|
||||
│ Send Request │ │ NoInternetException│
|
||||
│ via DioClient │ └────────────────────┘
|
||||
└──────┬───────────┘ │
|
||||
│ │
|
||||
├─── Timeout ──────────────────────────────┤
|
||||
│ │
|
||||
├─── Connection Error ─────────────────────┤
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────┐ ┌────────────────────┐
|
||||
│ Retry Interceptor│───── Max retries ──│ TimeoutException │
|
||||
│ (3 attempts) │ reached │ ConnectionException│
|
||||
└──────┬───────────┘ └────────────────────┘
|
||||
│ │
|
||||
│ Success after retry │
|
||||
▼ ▼
|
||||
┌──────────────────┐ ┌────────────────────┐
|
||||
│ Error Interceptor│ │ Repository │
|
||||
│ (Status codes) │ │ Converts to │
|
||||
└──────┬───────────┘ │ Failure │
|
||||
│ └────────────────────┘
|
||||
│ │
|
||||
├─── 400 ──────── BadRequestException │
|
||||
├─── 401 ──────── UnauthorizedException │
|
||||
├─── 403 ──────── ForbiddenException │
|
||||
├─── 404 ──────── NotFoundException │
|
||||
├─── 422 ──────── ValidationException │
|
||||
├─── 429 ──────── RateLimitException │
|
||||
├─── 500+ ─────── ServerException │
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────┐ ┌────────────────────┐
|
||||
│ Success Response │ │ UI Layer │
|
||||
│ Parse JSON │ │ Shows Error │
|
||||
│ Return Model │ │ Message │
|
||||
└──────────────────┘ └────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Authentication Flow
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ User Login │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Auth API Call │
|
||||
│ POST /login │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────┐
|
||||
│ Receive JWT Token │
|
||||
│ { token: "...", │
|
||||
│ refreshToken: "..." }│
|
||||
└────────┬────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────┐
|
||||
│ AuthInterceptor │
|
||||
│ .setAuthToken(token) │
|
||||
└────────┬────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────┐
|
||||
│ All subsequent requests │
|
||||
│ include: │
|
||||
│ Authorization: │
|
||||
│ Bearer <token> │
|
||||
└────────┬────────────────┘
|
||||
│
|
||||
│
|
||||
├──── Token Valid ────────► Continue Request
|
||||
│
|
||||
├──── 401 Unauthorized ───┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────────────┐ ┌────────────────────┐
|
||||
│ AuthInterceptor detects │ │ Refresh Token │
|
||||
│ 401 response │ │ POST /refresh │
|
||||
└────────┬────────────────┘ └────────┬───────────┘
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌────────────────────┐
|
||||
│ │ Get New Token │
|
||||
│ └────────┬───────────┘
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌────────────────────┐
|
||||
│ │ Update Token │
|
||||
│ │ Retry Request │
|
||||
│ └────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────┐
|
||||
│ Refresh Failed? │
|
||||
│ Clear token │
|
||||
│ Navigate to Login │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Synchronization Flow
|
||||
|
||||
```
|
||||
┌────────────────┐
|
||||
│ App Launch │
|
||||
└───────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────┐
|
||||
│ Load from Hive Cache │
|
||||
│ (Instant UI) │
|
||||
└───────┬────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────┐
|
||||
│ Check Network Status │
|
||||
└───────┬────────────────┘
|
||||
│
|
||||
├─── Offline ──────────────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────────────────────┐ ┌──────────────────┐
|
||||
│ Fetch from API │ │ Use Cached Data │
|
||||
│ (Background) │ │ Show Offline UI │
|
||||
└───────┬────────────────┘ └──────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────┐
|
||||
│ Compare with Cache │
|
||||
│ (by timestamp) │
|
||||
└───────┬────────────────┘
|
||||
│
|
||||
├─── New Data Available ──┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────────────────────┐ ┌──────────────────────┐
|
||||
│ Update Hive Cache │ │ No Changes │
|
||||
└───────┬────────────────┘ │ Keep Current Data │
|
||||
│ └──────────────────────┘
|
||||
▼
|
||||
┌────────────────────────┐
|
||||
│ Notify UI │
|
||||
│ (Riverpod ref.refresh) │
|
||||
└───────┬────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────┐
|
||||
│ UI Updates │
|
||||
│ Show Fresh Data │
|
||||
└────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Dependencies
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ GetIt Service Locator │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────┐ │
|
||||
│ │ Connectivity (External) │ │
|
||||
│ └────────────┬───────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────────────────────────────────┐ │
|
||||
│ │ NetworkInfo │ │
|
||||
│ │ - NetworkInfoImpl(Connectivity) │ │
|
||||
│ └────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────┐ │
|
||||
│ │ DioClient │ │
|
||||
│ │ - Dio instance │ │
|
||||
│ │ - Interceptors │ │
|
||||
│ └────────────┬───────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────────────────────────────────┐ │
|
||||
│ │ ProductRemoteDataSource │ │
|
||||
│ │ - ProductRemoteDataSourceImpl(Dio) │ │
|
||||
│ │ OR │ │
|
||||
│ │ - ProductRemoteDataSourceMock() │ │
|
||||
│ └────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────┐ │
|
||||
│ │ CategoryRemoteDataSource │ │
|
||||
│ │ - CategoryRemoteDataSourceImpl(Dio) │ │
|
||||
│ │ OR │ │
|
||||
│ │ - CategoryRemoteDataSourceMock() │ │
|
||||
│ └────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ (Future: Repositories, UseCases) │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Dependencies Graph
|
||||
|
||||
```
|
||||
main.dart
|
||||
│
|
||||
├─► injection_container.dart
|
||||
│ │
|
||||
│ ├─► dio_client.dart
|
||||
│ │ │
|
||||
│ │ └─► api_interceptor.dart
|
||||
│ │ │
|
||||
│ │ └─► api_constants.dart
|
||||
│ │ └─► exceptions.dart
|
||||
│ │
|
||||
│ ├─► network_info.dart
|
||||
│ │ │
|
||||
│ │ └─► connectivity_plus
|
||||
│ │
|
||||
│ ├─► product_remote_datasource.dart
|
||||
│ │ │
|
||||
│ │ ├─► dio_client.dart
|
||||
│ │ ├─► product_model.dart
|
||||
│ │ ├─► api_constants.dart
|
||||
│ │ └─► exceptions.dart
|
||||
│ │
|
||||
│ └─► category_remote_datasource.dart
|
||||
│ │
|
||||
│ ├─► dio_client.dart
|
||||
│ ├─► category_model.dart
|
||||
│ ├─► api_constants.dart
|
||||
│ └─► exceptions.dart
|
||||
│
|
||||
└─► app.dart (Riverpod providers)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ TESTING PYRAMID │
|
||||
│ │
|
||||
│ ┌───────┐ │
|
||||
│ │ E2E │ │
|
||||
│ │ Tests │ │
|
||||
│ └───┬───┘ │
|
||||
│ │ │
|
||||
│ ┌───────┴───────┐ │
|
||||
│ │ Integration │ │
|
||||
│ │ Tests │ │
|
||||
│ └───────┬───────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────┴───────────┐ │
|
||||
│ │ Widget Tests │ │
|
||||
│ │ (with mock providers)│ │
|
||||
│ └───────────┬───────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────────┴───────────────┐ │
|
||||
│ │ Unit Tests │ │
|
||||
│ │ - Data Sources (Mock/Real) │ │
|
||||
│ │ - Network Info │ │
|
||||
│ │ - DioClient │ │
|
||||
│ │ - Interceptors │ │
|
||||
│ │ - Exception Handling │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
|
||||
Test Coverage Goals:
|
||||
- Unit Tests: 80%+
|
||||
- Widget Tests: 60%+
|
||||
- Integration Tests: Key flows
|
||||
- E2E Tests: Critical paths
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Architecture Status**: ✅ Complete
|
||||
|
||||
This architecture provides:
|
||||
- Clean separation of concerns
|
||||
- Offline-first capability
|
||||
- Robust error handling
|
||||
- Easy testing and mocking
|
||||
- Scalable and maintainable structure
|
||||
699
docs/API_INTEGRATION_GUIDE.md
Normal file
699
docs/API_INTEGRATION_GUIDE.md
Normal file
@@ -0,0 +1,699 @@
|
||||
# API Integration Guide - Retail POS App
|
||||
|
||||
## Overview
|
||||
|
||||
This guide provides comprehensive documentation for the API integration layer of the Retail POS application. The integration is built using **Dio** for HTTP requests with a robust error handling, retry logic, and offline-first architecture.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Layered Architecture
|
||||
|
||||
```
|
||||
Presentation Layer (UI)
|
||||
<20>
|
||||
Providers (Riverpod)
|
||||
<20>
|
||||
Use Cases
|
||||
<20>
|
||||
Repositories
|
||||
<20>
|
||||
Data Sources (Remote + Local)
|
||||
<20>
|
||||
Network Layer (Dio)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Components
|
||||
|
||||
### 1. DioClient (`/lib/core/network/dio_client.dart`)
|
||||
|
||||
**Purpose**: Centralized HTTP client configuration with Dio.
|
||||
|
||||
**Features**:
|
||||
- Base URL and timeout configuration
|
||||
- Request/response interceptors
|
||||
- Authentication header management
|
||||
- Automatic retry logic
|
||||
- Error handling and conversion to custom exceptions
|
||||
|
||||
**Usage Example**:
|
||||
```dart
|
||||
final dioClient = DioClient();
|
||||
|
||||
// GET request
|
||||
final response = await dioClient.get('/products');
|
||||
|
||||
// POST request
|
||||
final response = await dioClient.post(
|
||||
'/products',
|
||||
data: {'name': 'Product Name', 'price': 29.99},
|
||||
);
|
||||
|
||||
// With query parameters
|
||||
final response = await dioClient.get(
|
||||
'/products/search',
|
||||
queryParameters: {'q': 'laptop'},
|
||||
);
|
||||
```
|
||||
|
||||
**Methods**:
|
||||
- `get()` - GET requests
|
||||
- `post()` - POST requests
|
||||
- `put()` - PUT requests
|
||||
- `delete()` - DELETE requests
|
||||
- `patch()` - PATCH requests
|
||||
- `download()` - File downloads
|
||||
- `updateAuthToken()` - Update authentication token
|
||||
- `removeAuthToken()` - Remove authentication token
|
||||
- `addHeader()` - Add custom header
|
||||
- `removeHeader()` - Remove custom header
|
||||
|
||||
---
|
||||
|
||||
### 2. API Constants (`/lib/core/constants/api_constants.dart`)
|
||||
|
||||
**Purpose**: Centralized configuration for API endpoints, timeouts, and settings.
|
||||
|
||||
**Configuration**:
|
||||
```dart
|
||||
// Base URL
|
||||
static const String baseUrl = 'https://api.retailpos.example.com';
|
||||
static const String apiVersion = '/api/v1';
|
||||
|
||||
// Timeouts (in milliseconds)
|
||||
static const int connectTimeout = 30000;
|
||||
static const int receiveTimeout = 30000;
|
||||
static const int sendTimeout = 30000;
|
||||
|
||||
// Retry configuration
|
||||
static const int maxRetries = 3;
|
||||
static const int retryDelay = 1000;
|
||||
```
|
||||
|
||||
**Endpoints**:
|
||||
```dart
|
||||
// Products
|
||||
ApiConstants.products // GET /products
|
||||
ApiConstants.productById('123') // GET /products/123
|
||||
ApiConstants.productsByCategory('cat1') // GET /products/category/cat1
|
||||
ApiConstants.searchProducts // GET /products/search?q=query
|
||||
ApiConstants.syncProducts // POST /products/sync
|
||||
|
||||
// Categories
|
||||
ApiConstants.categories // GET /categories
|
||||
ApiConstants.categoryById('123') // GET /categories/123
|
||||
ApiConstants.syncCategories // POST /categories/sync
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Network Info (`/lib/core/network/network_info.dart`)
|
||||
|
||||
**Purpose**: Check network connectivity status using `connectivity_plus`.
|
||||
|
||||
**Features**:
|
||||
- Check current connectivity status
|
||||
- Stream of connectivity changes
|
||||
- Check connection type (WiFi, Mobile, etc.)
|
||||
|
||||
**Usage Example**:
|
||||
```dart
|
||||
final networkInfo = NetworkInfoImpl(Connectivity());
|
||||
|
||||
// Check if connected
|
||||
final isConnected = await networkInfo.isConnected;
|
||||
|
||||
// Listen to connectivity changes
|
||||
networkInfo.onConnectivityChanged.listen((isConnected) {
|
||||
if (isConnected) {
|
||||
print('Device is online');
|
||||
} else {
|
||||
print('Device is offline');
|
||||
}
|
||||
});
|
||||
|
||||
// Check connection type
|
||||
final isWiFi = await networkInfo.isConnectedViaWiFi;
|
||||
final isMobile = await networkInfo.isConnectedViaMobile;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. API Interceptors (`/lib/core/network/api_interceptor.dart`)
|
||||
|
||||
#### Logging Interceptor
|
||||
Logs all requests and responses for debugging.
|
||||
|
||||
```dart
|
||||
REQUEST[GET] => PATH: /products
|
||||
Headers: {Content-Type: application/json}
|
||||
RESPONSE[200] => PATH: /products
|
||||
Data: {...}
|
||||
```
|
||||
|
||||
#### Authentication Interceptor
|
||||
Automatically adds authentication headers to requests.
|
||||
|
||||
```dart
|
||||
// Set auth token
|
||||
authInterceptor.setAuthToken('your-jwt-token');
|
||||
|
||||
// All requests now include:
|
||||
// Authorization: Bearer your-jwt-token
|
||||
|
||||
// Clear token
|
||||
authInterceptor.clearAuthToken();
|
||||
```
|
||||
|
||||
#### Error Interceptor
|
||||
Converts HTTP status codes to custom exceptions.
|
||||
|
||||
**Status Code Mapping**:
|
||||
- `400` <20> `BadRequestException`
|
||||
- `401` <20> `UnauthorizedException`
|
||||
- `403` <20> `ForbiddenException`
|
||||
- `404` <20> `NotFoundException`
|
||||
- `422` <20> `ValidationException`
|
||||
- `429` <20> `RateLimitException`
|
||||
- `500+` <20> `ServerException`
|
||||
- `503` <20> `ServiceUnavailableException`
|
||||
|
||||
#### Retry Interceptor
|
||||
Automatically retries failed requests.
|
||||
|
||||
**Retry Conditions**:
|
||||
- Connection timeout
|
||||
- Send/receive timeout
|
||||
- Connection errors
|
||||
- HTTP 408, 429, 502, 503, 504
|
||||
|
||||
**Retry Strategy**:
|
||||
- Max retries: 3 (configurable)
|
||||
- Exponential backoff: delay * (retry_count + 1)
|
||||
- Default delay: 1000ms
|
||||
|
||||
---
|
||||
|
||||
### 5. Custom Exceptions (`/lib/core/errors/exceptions.dart`)
|
||||
|
||||
**Network Exceptions**:
|
||||
- `NoInternetException` - No internet connection
|
||||
- `TimeoutException` - Request timeout
|
||||
- `ConnectionException` - Connection failed
|
||||
- `NetworkException` - General network error
|
||||
|
||||
**Server Exceptions**:
|
||||
- `ServerException` - Server error (500+)
|
||||
- `ServiceUnavailableException` - Service unavailable (503)
|
||||
|
||||
**Client Exceptions**:
|
||||
- `BadRequestException` - Invalid request (400)
|
||||
- `UnauthorizedException` - Authentication required (401)
|
||||
- `ForbiddenException` - Access forbidden (403)
|
||||
- `NotFoundException` - Resource not found (404)
|
||||
- `ValidationException` - Validation failed (422)
|
||||
- `RateLimitException` - Rate limit exceeded (429)
|
||||
|
||||
**Cache Exceptions**:
|
||||
- `CacheException` - Cache operation failed
|
||||
- `CacheNotFoundException` - Data not found in cache
|
||||
|
||||
**Data Exceptions**:
|
||||
- `DataParsingException` - Failed to parse data
|
||||
- `InvalidDataFormatException` - Invalid data format
|
||||
|
||||
**Business Exceptions**:
|
||||
- `OutOfStockException` - Product out of stock
|
||||
- `InsufficientStockException` - Insufficient stock
|
||||
- `TransactionException` - Transaction failed
|
||||
- `PaymentException` - Payment processing failed
|
||||
|
||||
---
|
||||
|
||||
### 6. Failure Classes (`/lib/core/errors/failures.dart`)
|
||||
|
||||
Failures are used in the domain/presentation layer to represent errors without throwing exceptions.
|
||||
|
||||
**Network Failures**:
|
||||
- `NoInternetFailure`
|
||||
- `ConnectionFailure`
|
||||
- `TimeoutFailure`
|
||||
- `NetworkFailure`
|
||||
|
||||
**Server Failures**:
|
||||
- `ServerFailure`
|
||||
- `ServiceUnavailableFailure`
|
||||
|
||||
**Client Failures**:
|
||||
- `BadRequestFailure`
|
||||
- `UnauthorizedFailure`
|
||||
- `ForbiddenFailure`
|
||||
- `NotFoundFailure`
|
||||
- `ValidationFailure`
|
||||
- `RateLimitFailure`
|
||||
|
||||
**Usage Pattern**:
|
||||
```dart
|
||||
// In repository
|
||||
try {
|
||||
final products = await remoteDataSource.fetchProducts();
|
||||
return Right(products);
|
||||
} on NoInternetException {
|
||||
return Left(NoInternetFailure());
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message, e.statusCode));
|
||||
} catch (e) {
|
||||
return Left(NetworkFailure('Unexpected error: ${e.toString()}'));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. Remote Data Sources
|
||||
|
||||
#### Product Remote Data Source (`/lib/features/products/data/datasources/product_remote_datasource.dart`)
|
||||
|
||||
**Methods**:
|
||||
```dart
|
||||
// Fetch all products
|
||||
Future<List<ProductModel>> fetchProducts();
|
||||
|
||||
// Fetch single product
|
||||
Future<ProductModel> fetchProductById(String id);
|
||||
|
||||
// Fetch products by category
|
||||
Future<List<ProductModel>> fetchProductsByCategory(String categoryId);
|
||||
|
||||
// Search products
|
||||
Future<List<ProductModel>> searchProducts(String query);
|
||||
|
||||
// Sync products
|
||||
Future<void> syncProducts(List<ProductModel> products);
|
||||
```
|
||||
|
||||
**Usage Example**:
|
||||
```dart
|
||||
final dataSource = ProductRemoteDataSourceImpl(dioClient);
|
||||
|
||||
// Fetch all products
|
||||
final products = await dataSource.fetchProducts();
|
||||
|
||||
// Search products
|
||||
final results = await dataSource.searchProducts('laptop');
|
||||
|
||||
// Fetch by category
|
||||
final categoryProducts = await dataSource.fetchProductsByCategory('electronics');
|
||||
```
|
||||
|
||||
#### Category Remote Data Source (`/lib/features/categories/data/datasources/category_remote_datasource.dart`)
|
||||
|
||||
**Methods**:
|
||||
```dart
|
||||
// Fetch all categories
|
||||
Future<List<CategoryModel>> fetchCategories();
|
||||
|
||||
// Fetch single category
|
||||
Future<CategoryModel> fetchCategoryById(String id);
|
||||
|
||||
// Sync categories
|
||||
Future<void> syncCategories(List<CategoryModel> categories);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Setup & Installation
|
||||
|
||||
### 1. Dependencies
|
||||
|
||||
Already added to `pubspec.yaml`:
|
||||
```yaml
|
||||
dependencies:
|
||||
dio: ^5.7.0
|
||||
connectivity_plus: ^6.1.1
|
||||
equatable: ^2.0.7
|
||||
get_it: ^8.0.4
|
||||
```
|
||||
|
||||
### 2. Initialize Dependencies
|
||||
|
||||
In `main.dart`:
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'core/di/injection_container.dart' as di;
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Initialize dependencies
|
||||
await di.initDependencies();
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Configure API Base URL
|
||||
|
||||
Update in `/lib/core/constants/api_constants.dart`:
|
||||
```dart
|
||||
static const String baseUrl = 'https://your-api-url.com';
|
||||
```
|
||||
|
||||
### 4. Using Mock Data (Development)
|
||||
|
||||
Enable mock data in `/lib/core/constants/api_constants.dart`:
|
||||
```dart
|
||||
static const bool useMockData = true;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Example 1: Fetch Products in a Repository
|
||||
|
||||
```dart
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../../core/errors/exceptions.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../../domain/repositories/product_repository.dart';
|
||||
import '../datasources/product_remote_datasource.dart';
|
||||
import '../models/product_model.dart';
|
||||
|
||||
class ProductRepositoryImpl implements ProductRepository {
|
||||
final ProductRemoteDataSource remoteDataSource;
|
||||
final NetworkInfo networkInfo;
|
||||
|
||||
ProductRepositoryImpl({
|
||||
required this.remoteDataSource,
|
||||
required this.networkInfo,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<ProductModel>>> getProducts() async {
|
||||
// Check network connectivity
|
||||
if (!await networkInfo.isConnected) {
|
||||
return Left(NoInternetFailure());
|
||||
}
|
||||
|
||||
try {
|
||||
final products = await remoteDataSource.fetchProducts();
|
||||
return Right(products);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message, e.statusCode));
|
||||
} on TimeoutException {
|
||||
return Left(TimeoutFailure());
|
||||
} on NetworkException catch (e) {
|
||||
return Left(NetworkFailure(e.message, e.statusCode));
|
||||
} catch (e) {
|
||||
return Left(NetworkFailure('Unexpected error: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: Using in a Riverpod Provider
|
||||
|
||||
```dart
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import '../../../core/di/injection_container.dart';
|
||||
import '../../data/datasources/product_remote_datasource.dart';
|
||||
import '../../data/models/product_model.dart';
|
||||
|
||||
part 'products_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
class Products extends _$Products {
|
||||
@override
|
||||
Future<List<ProductModel>> build() async {
|
||||
final dataSource = sl<ProductRemoteDataSource>();
|
||||
return await dataSource.fetchProducts();
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
state = const AsyncValue.loading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
final dataSource = sl<ProductRemoteDataSource>();
|
||||
return await dataSource.fetchProducts();
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Handling Errors in UI
|
||||
|
||||
```dart
|
||||
// In your widget
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final productsAsync = ref.watch(productsProvider);
|
||||
|
||||
return productsAsync.when(
|
||||
data: (products) => ProductGrid(products: products),
|
||||
loading: () => const CircularProgressIndicator(),
|
||||
error: (error, stack) {
|
||||
// Handle different error types
|
||||
if (error is NoInternetFailure) {
|
||||
return ErrorWidget(
|
||||
message: 'No internet connection',
|
||||
onRetry: () => ref.refresh(productsProvider),
|
||||
);
|
||||
} else if (error is ServerFailure) {
|
||||
return ErrorWidget(
|
||||
message: 'Server error. Please try again later',
|
||||
onRetry: () => ref.refresh(productsProvider),
|
||||
);
|
||||
}
|
||||
return ErrorWidget(
|
||||
message: 'An error occurred',
|
||||
onRetry: () => ref.refresh(productsProvider),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Testing Network Connectivity
|
||||
|
||||
```dart
|
||||
// Use mock implementation
|
||||
final mockNetworkInfo = NetworkInfoMock(isConnected: false);
|
||||
|
||||
// Test offline scenario
|
||||
final repository = ProductRepositoryImpl(
|
||||
remoteDataSource: mockRemoteDataSource,
|
||||
networkInfo: mockNetworkInfo,
|
||||
);
|
||||
|
||||
final result = await repository.getProducts();
|
||||
expect(result.isLeft(), true);
|
||||
expect(result.fold((l) => l, (r) => null), isA<NoInternetFailure>());
|
||||
```
|
||||
|
||||
### Testing API Calls
|
||||
|
||||
```dart
|
||||
// Use mock data source
|
||||
final mockDataSource = ProductRemoteDataSourceMock();
|
||||
|
||||
final products = await mockDataSource.fetchProducts();
|
||||
expect(products, isNotEmpty);
|
||||
expect(products.first.name, 'Sample Product 1');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Response Format
|
||||
|
||||
### Expected JSON Response Formats
|
||||
|
||||
#### Products List
|
||||
```json
|
||||
{
|
||||
"products": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "Product Name",
|
||||
"description": "Product description",
|
||||
"price": 29.99,
|
||||
"imageUrl": "https://example.com/image.jpg",
|
||||
"categoryId": "cat1",
|
||||
"stockQuantity": 100,
|
||||
"isAvailable": true,
|
||||
"createdAt": "2024-01-01T00:00:00Z",
|
||||
"updatedAt": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Or direct array:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "1",
|
||||
"name": "Product Name",
|
||||
...
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### Single Product
|
||||
```json
|
||||
{
|
||||
"product": {
|
||||
"id": "1",
|
||||
"name": "Product Name",
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Categories List
|
||||
```json
|
||||
{
|
||||
"categories": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "Electronics",
|
||||
"description": "Electronic devices",
|
||||
"iconPath": "assets/icons/electronics.png",
|
||||
"color": "#2196F3",
|
||||
"productCount": 25,
|
||||
"createdAt": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Error Response
|
||||
```json
|
||||
{
|
||||
"message": "Error message",
|
||||
"error": "Detailed error",
|
||||
"errors": {
|
||||
"field1": ["Validation error 1"],
|
||||
"field2": ["Validation error 2"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: Connection Timeout
|
||||
|
||||
**Solution**:
|
||||
- Increase timeout in `api_constants.dart`
|
||||
- Check network connectivity
|
||||
- Verify API endpoint is accessible
|
||||
|
||||
### Issue: SSL Certificate Error
|
||||
|
||||
**Solution**:
|
||||
In development, you can allow self-signed certificates:
|
||||
```dart
|
||||
(_dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
|
||||
final client = HttpClient();
|
||||
client.badCertificateCallback = (cert, host, port) => true;
|
||||
return client;
|
||||
};
|
||||
```
|
||||
|
||||
**<EFBFBD> Warning**: Never use this in production!
|
||||
|
||||
### Issue: 401 Unauthorized
|
||||
|
||||
**Solution**:
|
||||
- Check authentication token is set correctly
|
||||
- Verify token hasn't expired
|
||||
- Refresh token if necessary
|
||||
|
||||
### Issue: Data Parsing Error
|
||||
|
||||
**Solution**:
|
||||
- Verify API response format matches expected format
|
||||
- Check JSON field names match model properties
|
||||
- Add debug logging to see actual response
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always check network connectivity** before making API calls
|
||||
2. **Use offline-first approach** - read from cache first, sync in background
|
||||
3. **Handle all error scenarios** gracefully with user-friendly messages
|
||||
4. **Implement proper retry logic** for transient errors
|
||||
5. **Log requests/responses** in development, disable in production
|
||||
6. **Use mock data sources** for development and testing
|
||||
7. **Implement request cancellation** for expensive operations
|
||||
8. **Cache API responses** to improve performance
|
||||
9. **Use proper timeout values** based on expected response time
|
||||
10. **Implement proper authentication** token management
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Implement Repositories**: Create repository implementations
|
||||
2. **Add Use Cases**: Define business logic in use cases
|
||||
3. **Create Riverpod Providers**: Wire up data layer with UI
|
||||
4. **Implement Offline Sync**: Add background sync logic
|
||||
5. **Add Authentication**: Implement OAuth/JWT authentication
|
||||
6. **Implement Caching**: Add response caching with Hive
|
||||
7. **Add Pagination**: Implement paginated API requests
|
||||
8. **Error Tracking**: Integrate error tracking (Sentry, Firebase Crashlytics)
|
||||
|
||||
---
|
||||
|
||||
## Support & Resources
|
||||
|
||||
- **Dio Documentation**: https://pub.dev/packages/dio
|
||||
- **Connectivity Plus**: https://pub.dev/packages/connectivity_plus
|
||||
- **GetIt Documentation**: https://pub.dev/packages/get_it
|
||||
- **Riverpod Documentation**: https://riverpod.dev
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
lib/
|
||||
| ||||