This commit is contained in:
2025-10-28 00:09:46 +07:00
parent 9ebe7c2919
commit de49f564b1
110 changed files with 15392 additions and 3996 deletions

452
API_CLIENT_SETUP.md Normal file
View File

@@ -0,0 +1,452 @@
# API Client Setup - Complete Implementation
## Overview
I have created a robust API client for your Flutter warehouse management app with comprehensive features including:
- **Automatic token management** from secure storage
- **401 error handling** with automatic token clearing
- **Request/response logging** with sensitive data redaction
- **Configurable timeouts** (30 seconds)
- **Proper error transformation** to custom exceptions
- **Support for all HTTP methods** (GET, POST, PUT, DELETE)
## Files Created
### 1. Core Network Files
#### `/lib/core/network/api_client.dart`
- Main API client implementation using Dio
- Automatic Bearer token injection from secure storage
- Request/response/error interceptors with comprehensive logging
- 401 error handler that clears tokens and triggers logout callback
- Methods: `get()`, `post()`, `put()`, `delete()`
- Utility methods: `testConnection()`, `isAuthenticated()`, `getAccessToken()`, `clearAuth()`
#### `/lib/core/network/api_response.dart`
- Generic API response wrapper matching your backend format
- Structure: `Value`, `IsSuccess`, `IsFailure`, `Errors`, `ErrorCodes`
- Helper methods: `hasData`, `getErrorMessage()`, `getAllErrorsAsString()`, `hasErrorCode()`
#### `/lib/core/network/api_client_example.dart`
- Comprehensive usage examples for all scenarios
- Examples for: Login, GET/POST/PUT/DELETE requests, error handling, etc.
#### `/lib/core/network/README.md`
- Complete documentation for the API client
- Usage guides, best practices, troubleshooting
### 2. Secure Storage
#### `/lib/core/storage/secure_storage.dart`
- Singleton wrapper for flutter_secure_storage
- Token management: `saveAccessToken()`, `getAccessToken()`, `clearTokens()`
- User data: `saveUserId()`, `saveUsername()`, etc.
- Utility methods: `isAuthenticated()`, `clearAll()`, `containsKey()`
- Platform-specific secure options (Android: encrypted shared prefs, iOS: Keychain)
### 3. Constants
#### `/lib/core/constants/api_endpoints.dart`
- Centralized API endpoint definitions
- Authentication: `/auth/login`, `/auth/logout`
- Warehouses: `/warehouses`
- Products: `/products` with query parameter helpers
- Scans: `/api/scans`
### 4. Core Exports
#### `/lib/core/core.dart` (Updated)
- Added exports for new modules:
- `api_endpoints.dart`
- `api_response.dart`
- `secure_storage.dart`
### 5. Dependencies
#### `pubspec.yaml` (Updated)
- Added `flutter_secure_storage: ^9.0.0`
## Key Features
### 1. Automatic Token Management
The API client automatically injects Bearer tokens into all requests:
```dart
// Initialize with secure storage
final secureStorage = SecureStorage();
final apiClient = ApiClient(secureStorage);
// Token is automatically added to all requests
final response = await apiClient.get('/warehouses');
// Request header: Authorization: Bearer <token>
```
### 2. 401 Error Handling
When a 401 Unauthorized error occurs:
1. Error is logged to console
2. All tokens are cleared from secure storage
3. `onUnauthorized` callback is triggered
4. App can navigate to login screen
```dart
final apiClient = ApiClient(
secureStorage,
onUnauthorized: () {
// This callback is triggered on 401 errors
context.go('/login'); // Navigate to login
},
);
```
### 3. Comprehensive Logging
All requests, responses, and errors are logged with sensitive data redacted:
```
REQUEST[GET] => https://api.example.com/warehouses
Headers: {Authorization: ***REDACTED***, Content-Type: application/json}
RESPONSE[200] => https://api.example.com/warehouses
Data: {...}
ERROR[401] => https://api.example.com/products
401 Unauthorized - Clearing tokens and triggering logout
```
### 4. Error Handling
Dio exceptions are transformed into custom app exceptions:
```dart
try {
final response = await apiClient.get('/products');
} on NetworkException catch (e) {
// Timeout, no internet, etc.
print('Network error: ${e.message}');
} on ServerException catch (e) {
// 4xx, 5xx errors
print('Server error: ${e.message}');
print('Error code: ${e.code}'); // e.g., '401', '404', '500'
}
```
Specific error codes:
- `401`: Unauthorized (automatically handled)
- `403`: Forbidden
- `404`: Not Found
- `422`: Validation Error
- `429`: Rate Limited
- `500-504`: Server Errors
### 5. API Response Parsing
All API responses follow the standard format:
```dart
final response = await apiClient.get('/warehouses');
final apiResponse = ApiResponse.fromJson(
response.data,
(json) => (json as List).map((e) => Warehouse.fromJson(e)).toList(),
);
if (apiResponse.isSuccess && apiResponse.value != null) {
final warehouses = apiResponse.value;
print('Found ${warehouses.length} warehouses');
} else {
print('Error: ${apiResponse.getErrorMessage()}');
}
```
## Usage Examples
### Initialize API Client
```dart
import 'package:minhthu/core/core.dart';
final secureStorage = SecureStorage();
final apiClient = ApiClient(
secureStorage,
onUnauthorized: () {
// Navigate to login on 401 errors
context.go('/login');
},
);
```
### Login Flow
```dart
// 1. Login request
final response = await apiClient.post(
ApiEndpoints.login,
data: {
'username': 'user@example.com',
'password': 'password123',
},
);
// 2. Parse response
final apiResponse = ApiResponse.fromJson(
response.data,
(json) => User.fromJson(json),
);
// 3. Save tokens (typically done in LoginUseCase)
if (apiResponse.isSuccess && apiResponse.value != null) {
final user = apiResponse.value!;
await secureStorage.saveAccessToken(user.accessToken);
await secureStorage.saveRefreshToken(user.refreshToken);
await secureStorage.saveUserId(user.userId);
}
```
### Get Warehouses (Authenticated)
```dart
// Token is automatically added by the interceptor
final response = await apiClient.get(ApiEndpoints.warehouses);
final apiResponse = ApiResponse.fromJson(
response.data,
(json) => (json as List).map((e) => Warehouse.fromJson(e)).toList(),
);
if (apiResponse.isSuccess && apiResponse.value != null) {
final warehouses = apiResponse.value!;
// Use the data
}
```
### Get Products with Query Parameters
```dart
final response = await apiClient.get(
ApiEndpoints.products,
queryParameters: ApiEndpoints.productQueryParams(
warehouseId: 1,
type: 'import',
),
);
```
### Save Scan Data
```dart
final response = await apiClient.post(
ApiEndpoints.scans,
data: {
'barcode': '1234567890',
'field1': 'Value 1',
'field2': 'Value 2',
'field3': 'Value 3',
'field4': 'Value 4',
},
);
```
### Check Authentication Status
```dart
final isAuthenticated = await apiClient.isAuthenticated();
if (!isAuthenticated) {
// Navigate to login
}
```
### Logout
```dart
await apiClient.clearAuth(); // Clears all tokens
```
## Integration with Repository Pattern
The API client is designed to work with your clean architecture:
```dart
class WarehouseRemoteDataSourceImpl implements WarehouseRemoteDataSource {
final ApiClient apiClient;
WarehouseRemoteDataSourceImpl(this.apiClient);
@override
Future<List<Warehouse>> getWarehouses() async {
final response = await apiClient.get(ApiEndpoints.warehouses);
final apiResponse = ApiResponse.fromJson(
response.data,
(json) => (json as List).map((e) => Warehouse.fromJson(e)).toList(),
);
if (apiResponse.isSuccess && apiResponse.value != null) {
return apiResponse.value!;
} else {
throw ServerException(apiResponse.getErrorMessage());
}
}
}
```
## Configuration
### Timeouts (in `app_constants.dart`)
```dart
static const int connectionTimeout = 30000; // 30 seconds
static const int receiveTimeout = 30000; // 30 seconds
static const int sendTimeout = 30000; // 30 seconds
```
### Base URL (in `app_constants.dart`)
```dart
static const String apiBaseUrl = 'https://api.example.com';
```
Or update dynamically:
```dart
apiClient.updateBaseUrl('https://dev-api.example.com');
```
## Security Features
1. **Token Encryption**: Tokens stored using platform-specific secure storage
- Android: Encrypted SharedPreferences
- iOS: Keychain with `first_unlock` accessibility
2. **Automatic Token Clearing**: 401 errors automatically clear all tokens
3. **Log Sanitization**: Authorization headers redacted in logs
```
Headers: {Authorization: ***REDACTED***}
```
4. **Singleton Pattern**: SecureStorage uses singleton to prevent multiple instances
## Testing
To test the API connection:
```dart
final isConnected = await apiClient.testConnection();
if (!isConnected) {
print('Cannot connect to API');
}
```
## Dependency Injection (GetIt)
Register with GetIt service locator:
```dart
final getIt = GetIt.instance;
// Register SecureStorage
getIt.registerLazySingleton<SecureStorage>(() => SecureStorage());
// Register ApiClient
getIt.registerLazySingleton<ApiClient>(
() => ApiClient(
getIt<SecureStorage>(),
onUnauthorized: () {
// Handle unauthorized
},
),
);
```
## File Structure
```
lib/
core/
constants/
app_constants.dart # Existing - timeouts and base URL
api_endpoints.dart # NEW - API endpoint constants
network/
api_client.dart # UPDATED - Full implementation
api_response.dart # NEW - API response wrapper
api_client_example.dart # NEW - Usage examples
README.md # NEW - Documentation
storage/
secure_storage.dart # NEW - Secure storage wrapper
errors/
exceptions.dart # Existing - Used by API client
failures.dart # Existing - Used by repositories
core.dart # UPDATED - Added new exports
```
## Next Steps
1. **Update Existing Repositories**: Update your remote data sources to use the new API client
2. **Configure Base URL**: Set the correct API base URL in `app_constants.dart`
3. **Set Up Navigation**: Implement the `onUnauthorized` callback to navigate to login
4. **Add API Endpoints**: Add any missing endpoints to `api_endpoints.dart`
5. **Test Authentication Flow**: Test login, token injection, and 401 handling
## Testing the Setup
Run the example:
```dart
import 'package:minhthu/core/network/api_client_example.dart';
void main() async {
await runExamples();
}
```
## Troubleshooting
### Token not being injected
- Verify token is saved: `await secureStorage.getAccessToken()`
- Check logs for: `REQUEST[...] Headers: {Authorization: ***REDACTED***}`
### 401 errors not clearing tokens
- Verify `onUnauthorized` callback is set
- Check logs for: `401 Unauthorized - Clearing tokens and triggering logout`
### Connection timeouts
- Check network connectivity
- Verify base URL is correct
- Increase timeout values if needed
## Analysis Results
All files pass Flutter analysis with no issues:
- ✅ `api_client.dart` - No issues found
- ✅ `secure_storage.dart` - No issues found
- ✅ `api_response.dart` - No issues found
- ✅ `api_endpoints.dart` - No issues found
## Documentation
For detailed documentation, see:
- `/lib/core/network/README.md` - Complete API client documentation
- `/lib/core/network/api_client_example.dart` - Code examples
## Summary
The API client is production-ready with:
- ✅ Automatic token management from secure storage
- ✅ Request interceptor to inject Bearer tokens
- ✅ Response interceptor for logging
- ✅ Error interceptor to handle 401 errors
- ✅ Automatic token clearing on unauthorized access
- ✅ Comprehensive error handling
- ✅ Request/response logging with sensitive data redaction
- ✅ Support for all HTTP methods (GET, POST, PUT, DELETE)
- ✅ Configurable timeouts (30 seconds)
- ✅ Environment-specific base URLs
- ✅ Connection testing
- ✅ Clean integration with repository pattern
- ✅ Comprehensive documentation and examples
- ✅ All files pass static analysis
The API client is ready to use and follows Flutter best practices and clean architecture principles!