346 lines
8.2 KiB
Markdown
346 lines
8.2 KiB
Markdown
# API Integration - Quick Reference Card
|
|
|
|
## Essential Files
|
|
|
|
| File | Purpose | Key Methods |
|
|
|------|---------|-------------|
|
|
| `api_constants.dart` | API config | `baseUrl`, endpoints, timeouts |
|
|
| `dio_client.dart` | HTTP client | `get()`, `post()`, `put()`, `delete()` |
|
|
| `network_info.dart` | Connectivity | `isConnected`, `onConnectivityChanged` |
|
|
| `exceptions.dart` | Error types | 20+ exception classes |
|
|
| `failures.dart` | Domain errors | Failure classes for UI |
|
|
| `product_remote_datasource.dart` | Product API | `fetchProducts()`, `searchProducts()` |
|
|
| `category_remote_datasource.dart` | Category API | `fetchCategories()` |
|
|
|
|
## Quick Setup (5 Steps)
|
|
|
|
```dart
|
|
// 1. In main.dart - Initialize dependencies
|
|
import 'core/di/injection_container.dart' as di;
|
|
|
|
void main() async {
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
await di.initDependencies();
|
|
runApp(const MyApp());
|
|
}
|
|
|
|
// 2. Configure API URL in api_constants.dart
|
|
static const String baseUrl = 'https://your-api.com';
|
|
|
|
// 3. Enable mock data for testing (optional)
|
|
static const bool useMockData = true;
|
|
|
|
// 4. Get data source from service locator
|
|
final productDS = sl<ProductRemoteDataSource>();
|
|
|
|
// 5. Fetch data
|
|
final products = await productDS.fetchProducts();
|
|
```
|
|
|
|
## Common Operations
|
|
|
|
### Fetch All Products
|
|
```dart
|
|
final dataSource = sl<ProductRemoteDataSource>();
|
|
final products = await dataSource.fetchProducts();
|
|
```
|
|
|
|
### Search Products
|
|
```dart
|
|
final results = await dataSource.searchProducts('laptop');
|
|
```
|
|
|
|
### Fetch by Category
|
|
```dart
|
|
final products = await dataSource.fetchProductsByCategory('electronics');
|
|
```
|
|
|
|
### Fetch Single Product
|
|
```dart
|
|
final product = await dataSource.fetchProductById('123');
|
|
```
|
|
|
|
### Fetch Categories
|
|
```dart
|
|
final categoryDS = sl<CategoryRemoteDataSource>();
|
|
final categories = await categoryDS.fetchCategories();
|
|
```
|
|
|
|
### Check Connectivity
|
|
```dart
|
|
final networkInfo = sl<NetworkInfo>();
|
|
final isConnected = await networkInfo.isConnected;
|
|
```
|
|
|
|
## Error Handling Patterns
|
|
|
|
### Basic Try-Catch
|
|
```dart
|
|
try {
|
|
final products = await dataSource.fetchProducts();
|
|
// Use products
|
|
} on NoInternetException {
|
|
// Show "No internet" message
|
|
} on ServerException catch (e) {
|
|
// Show "Server error: ${e.message}"
|
|
} on NetworkException catch (e) {
|
|
// Show "Network error: ${e.message}"
|
|
}
|
|
```
|
|
|
|
### With Network Check
|
|
```dart
|
|
final networkInfo = sl<NetworkInfo>();
|
|
if (!await networkInfo.isConnected) {
|
|
// Load from cache or show offline UI
|
|
return;
|
|
}
|
|
|
|
try {
|
|
final products = await dataSource.fetchProducts();
|
|
// Use products
|
|
} catch (e) {
|
|
// Handle error
|
|
}
|
|
```
|
|
|
|
### Repository Pattern (Recommended)
|
|
```dart
|
|
Future<Either<Failure, List<Product>>> getProducts() async {
|
|
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 NetworkException catch (e) {
|
|
return Left(NetworkFailure(e.message));
|
|
}
|
|
}
|
|
```
|
|
|
|
## Exception → Failure Mapping
|
|
|
|
| Exception | Failure | UI Message |
|
|
|-----------|---------|------------|
|
|
| `NoInternetException` | `NoInternetFailure` | "No internet connection" |
|
|
| `TimeoutException` | `TimeoutFailure` | "Request timeout" |
|
|
| `ServerException (500+)` | `ServerFailure` | "Server error" |
|
|
| `UnauthorizedException (401)` | `UnauthorizedFailure` | "Please login" |
|
|
| `NotFoundException (404)` | `NotFoundFailure` | "Not found" |
|
|
| `ValidationException (422)` | `ValidationFailure` | "Invalid data" |
|
|
|
|
## Configuration Options
|
|
|
|
### API Constants
|
|
```dart
|
|
// In api_constants.dart
|
|
|
|
// Base URL
|
|
static const String baseUrl = 'https://api.example.com';
|
|
|
|
// Timeouts (milliseconds)
|
|
static const int connectTimeout = 30000;
|
|
static const int receiveTimeout = 30000;
|
|
|
|
// Retry
|
|
static const int maxRetries = 3;
|
|
static const int retryDelay = 1000;
|
|
|
|
// Mock data toggle
|
|
static const bool useMockData = false;
|
|
```
|
|
|
|
### DioClient Headers
|
|
```dart
|
|
final dioClient = DioClient();
|
|
|
|
// Add auth token
|
|
dioClient.updateAuthToken('your-jwt-token');
|
|
|
|
// Add custom header
|
|
dioClient.addHeader('X-Custom', 'value');
|
|
|
|
// Remove auth token
|
|
dioClient.removeAuthToken();
|
|
```
|
|
|
|
## Riverpod Integration
|
|
|
|
### Provider Setup
|
|
```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();
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
### UI Usage
|
|
```dart
|
|
Consumer(
|
|
builder: (context, ref, child) {
|
|
final productsAsync = ref.watch(productsProvider);
|
|
|
|
return productsAsync.when(
|
|
data: (products) => ProductList(products),
|
|
loading: () => CircularProgressIndicator(),
|
|
error: (error, stack) => ErrorWidget(error),
|
|
);
|
|
},
|
|
)
|
|
```
|
|
|
|
## API Endpoints Reference
|
|
|
|
### Products
|
|
| Method | Endpoint | Purpose |
|
|
|--------|----------|---------|
|
|
| GET | `/products` | Get all products |
|
|
| GET | `/products/:id` | Get single product |
|
|
| GET | `/products/category/:id` | Get by category |
|
|
| GET | `/products/search?q=query` | Search products |
|
|
| POST | `/products/sync` | Bulk sync |
|
|
|
|
### Categories
|
|
| Method | Endpoint | Purpose |
|
|
|--------|----------|---------|
|
|
| GET | `/categories` | Get all categories |
|
|
| GET | `/categories/:id` | Get single category |
|
|
| POST | `/categories/sync` | Bulk sync |
|
|
|
|
## Expected Response Formats
|
|
|
|
### Products List
|
|
```json
|
|
{
|
|
"products": [
|
|
{
|
|
"id": "1",
|
|
"name": "Product Name",
|
|
"price": 29.99,
|
|
"categoryId": "cat1",
|
|
"stockQuantity": 100,
|
|
"isAvailable": true,
|
|
"createdAt": "2024-01-01T00:00:00Z",
|
|
"updatedAt": "2024-01-01T00:00:00Z"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Categories List
|
|
```json
|
|
{
|
|
"categories": [
|
|
{
|
|
"id": "1",
|
|
"name": "Electronics",
|
|
"productCount": 25,
|
|
"createdAt": "2024-01-01T00:00:00Z"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Error Response
|
|
```json
|
|
{
|
|
"message": "Error message",
|
|
"error": "Detailed error"
|
|
}
|
|
```
|
|
|
|
## Testing Quick Start
|
|
|
|
### Use Mock Data Source
|
|
```dart
|
|
// In api_constants.dart
|
|
static const bool useMockData = true;
|
|
|
|
// Or directly
|
|
final mockDS = ProductRemoteDataSourceMock();
|
|
final products = await mockDS.fetchProducts();
|
|
```
|
|
|
|
### Mock Network Status
|
|
```dart
|
|
final mockNetwork = NetworkInfoMock(isConnected: false);
|
|
final isConnected = await mockNetwork.isConnected; // false
|
|
```
|
|
|
|
## Common Issues & Solutions
|
|
|
|
| Issue | Solution |
|
|
|-------|----------|
|
|
| Connection timeout | Increase timeout in `api_constants.dart` |
|
|
| SSL certificate error | Configure `badCertificateCallback` (dev only) |
|
|
| 401 Unauthorized | Update auth token with `updateAuthToken()` |
|
|
| Mock data not working | Check `useMockData = true` |
|
|
| Parsing error | Verify response format matches model |
|
|
|
|
## Performance Tips
|
|
|
|
1. **Enable caching**: Use Hive to cache API responses
|
|
2. **Pagination**: Request data in chunks (20-50 items)
|
|
3. **Debounce search**: Wait 300ms before searching
|
|
4. **Image optimization**: Use `cached_network_image`
|
|
5. **Background sync**: Sync during app idle time
|
|
6. **Cancel requests**: Use `CancelToken` for expensive operations
|
|
|
|
## Debug Logging
|
|
|
|
Logs are automatically generated:
|
|
```
|
|
REQUEST[GET] => PATH: /products
|
|
Headers: {Content-Type: application/json}
|
|
RESPONSE[200] => PATH: /products
|
|
Data: {...}
|
|
```
|
|
|
|
Disable in production by removing `LoggingInterceptor`.
|
|
|
|
## Security Checklist
|
|
|
|
- [ ] Use HTTPS (not HTTP)
|
|
- [ ] Store API keys securely (use `flutter_secure_storage`)
|
|
- [ ] Implement token refresh for expired tokens
|
|
- [ ] Validate SSL certificates in production
|
|
- [ ] Don't log sensitive data in production
|
|
- [ ] Use environment variables for API URLs
|
|
- [ ] Implement rate limiting on client side
|
|
|
|
## Next Steps
|
|
|
|
1. ✅ API integration complete
|
|
2. → Create Repository layer
|
|
3. → Implement Use Cases
|
|
4. → Wire up Riverpod Providers
|
|
5. → Build UI with error handling
|
|
6. → Add offline sync
|
|
7. → Implement authentication
|
|
8. → Add unit tests
|
|
|
|
---
|
|
|
|
**Quick Links:**
|
|
- Full Guide: `API_INTEGRATION_GUIDE.md`
|
|
- Architecture: `API_ARCHITECTURE.md`
|
|
- Examples: `examples/api_usage_example.dart`
|
|
|
|
**Status**: ✅ Ready to Use
|