Networking Architecture Documentation
This document explains how the networking layer works in the E2IP Mobile App, specifically focusing on the DioService and the various DataSource classes.
Table of Contents
Overview
The networking architecture follows a clean architecture pattern with clear separation of concerns:
- DioService: Core HTTP client wrapper that handles authentication, token refresh, and common HTTP operations
- DataSource Classes: Domain-specific classes that use DioService to interact with specific API endpoints
- Secure Storage: Manages token storage and retrieval securely
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Presentation │───▶│ DataSource │───▶│ DioService │
│ Layer │ │ Classes │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
┌─────────────────┐
│ Secure Storage │
│ Service │
└─────────────────┘
DioService
Purpose
DioService is a wrapper around the Dio HTTP client that provides:
- Automatic authentication token management
- Token refresh mechanism
- Standardized error handling
- Request/response interceptors
- DigiKey API token management
Key Features
1. Automatic Authentication
// Automatically adds Bearer token to requests
options.headers['Authorization'] = 'Bearer $token';
2. Token Refresh Mechanism
- Detects 401 Unauthorized responses
- Automatically attempts to refresh expired tokens
- Retries original request with new token
- Prevents multiple simultaneous refresh attempts
3. DigiKey Token Management
- Captures DigiKey tokens from response headers
- Stores them securely for future API calls
- Handles both access and refresh tokens
4. HTTP Methods
get(endpoint, queryParameters)- GET requests with automatic error handlinggetRaw(endpoint, queryParameters, options)- GET requests returning full Responsepost(endpoint, data, queryParameters, options)- POST requests with form data supportpostRaw(endpoint, data, queryParameters, options)- POST requests returning full Responseput(endpoint, data)- PUT requests for updates
5. Error Handling
Converts Dio exceptions into meaningful error messages: - Connection timeouts - Server errors with status codes - Network connectivity issues - Certificate problems
Configuration
DioService({
String? baseUrl, // API base URL (from .env file)
Map<String, dynamic>? headers // Custom headers
})
Default configuration:
- Base URL from environment variable BASE_URL
- Content-Type: application/json
- ngrok-skip-browser-warning: true (for development)
- Connect timeout: 35 seconds
- Receive timeout: 35 seconds
DataSource Classes
AuthDataSource
Purpose: Handles user authentication and session management
Key Methods:
- signIn(usernameOrEmail, password) - User login
- logout() - Clear stored tokens and user data
- isAuthenticated() - Check authentication status
Token Management: - Stores access_token, refresh_token, user_id, and admin status - Returns tuple with (isAuthenticated, isAdmin, userId)
ComponentDataSource
Purpose: Manages electronic component data and inventory operations
Key Methods:
- getComponents() - Fetch all components from local database
- searchComponents(keywords) - Search DigiKey API for components
- saveComponent(component, quantityLocalStock, minStock) - Add component to local inventory
- updateLocalStock(component, localStock, minLocalStock) - Update inventory levels
- checkoutComponent(component, quantity) - Remove components from inventory
Special Features: - DigiKey Integration: Handles both access and refresh tokens for DigiKey API - Fallback Token Strategy: Tries access token first, falls back to refresh token - Response Normalization: Handles different API response formats - Data Transformation: Converts between API formats and internal models
UserDataSource
Purpose: Manages user profile and settings
Key Methods:
- getUser(userId) - Fetch user profile
- updateUserProfile(userId, firstName, lastName, email) - Update profile information
- updateUserSettings(userId, notificationsEnabled, lowStockAlerts) - Update user preferences
Authentication Flow
Initial Login
1. User enters credentials
2. AuthDataSource.signIn() called
3. DioService.post() sends credentials to /api/auth/login
4. Server returns access_token, refresh_token, and user info
5. Tokens stored in SecureStorage
6. User roles and ID extracted and stored
Authenticated Requests
1. DataSource method called
2. DioService adds Authorization header automatically
3. Request sent to API
4. If 401 received, token refresh triggered
5. New tokens obtained and stored
6. Original request retried with new token
Token Refresh Process
1. 401 Unauthorized detected
2. Check if not already refreshing (prevent race conditions)
3. Extract refresh_token from SecureStorage
4. Send refresh request to /api/auth/refresh
5. Store new tokens
6. Retry original request
7. If refresh fails, clear all tokens
Error Handling
DioService Error Types
- Connection Timeout: Network connectivity issues
- Bad Response: Server errors (4xx, 5xx status codes)
- Request Cancelled: User cancelled request
- Bad Certificate: SSL/TLS certificate issues
- Connection Error: Network infrastructure problems
- Unknown Error: Unexpected errors including SocketException
DataSource Error Handling
Each DataSource wraps DioService calls in try-catch blocks and provides meaningful error messages:
try {
final response = await _dioService.get(endpoint);
return processResponse(response);
} catch (e) {
throw Exception('Failed to fetch data: $e');
}
Usage Examples
Basic Component Search
final componentDataSource = ComponentDataSource();
try {
final components = await componentDataSource.searchComponents('resistor 10k');
// Process components
} catch (e) {
// Handle error
print('Search failed: $e');
}
User Authentication
final authDataSource = AuthDataSource();
final (success, isAdmin, userId) = await authDataSource.signIn('user@example.com', 'password');
if (success) {
print('Login successful. Admin: $isAdmin, ID: $userId');
} else {
print('Login failed: $userId'); // userId contains error message on failure
}
Component Inventory Management
final componentDataSource = ComponentDataSource();
// Add component to inventory
await componentDataSource.saveComponent(component, 100, 10);
// Update stock levels
final updatedComponent = await componentDataSource.updateLocalStock(component, 50, 5);
// Checkout components
final afterCheckout = await componentDataSource.checkoutComponent(component, 5);
Security Considerations
- Secure Token Storage: All tokens stored using SecureStorageService
- Automatic Token Refresh: Prevents manual token handling in UI
- Request Validation: Server-side validation of all requests
- HTTPS Only: All API communication over secure connections
- Token Expiration: Automatic handling of expired tokens
Configuration Requirements
Environment Variables (.env file)
BASE_URL=https://your-api-server.com/api
Dependencies (pubspec.yaml)
dependencies:
dio: ^5.0.0
flutter_dotenv: ^5.0.0
# Add other networking dependencies
Troubleshooting
Common Issues
- 401 Unauthorized Errors
- Check if tokens are properly stored
- Verify token refresh mechanism is working
-
Ensure API endpoints are correct
-
Connection Timeouts
- Check network connectivity
- Verify API server is running
-
Consider increasing timeout values
-
DigiKey API Issues
- Verify DigiKey tokens are being captured from headers
- Check token storage keys match expected format
- Ensure fallback refresh token strategy is working
Debug Logging
The DioService includes debug logging for troubleshooting: - Request URLs and methods - Form data contents - Response status codes - Error details and types
Enable debug mode to see detailed networking logs in the console.