flutter_network_client 1.0.1
flutter_network_client: ^1.0.1 copied to clipboard
Advanced HTTP client for Flutter with authentication, retry logic, and clean architecture.
Flutter Network Client #
A simple, powerful HTTP client for Flutter with optional authentication, retry logic, and clean architecture. Built following SOLID principles with a focus on simplicity and flexibility - just like Dio or the standard HTTP package, but with built-in token management.
Features #
🚀 Simple & Powerful
- Support for all HTTP methods (GET, POST, PUT, DELETE, PATCH, HEAD)
- Automatic retry logic with exponential backoff
- Comprehensive error handling with specific exception types
- Customizable per-request options
🔐 Flexible Authentication
- Optional token management - use it only when you need it
- Multiple token types: Bearer, custom headers, query parameters
- Automatic token refresh with custom handlers
- Built-in SharedPreferences storage or custom providers
⚡ Performance & Reliability
- Connection pooling and timeout management
- Performance monitoring and metrics
- Network connectivity detection
- Request/response logging
🏗️ Clean Architecture & SOLID Principles
- Single Responsibility: Each class has one job
- Open/Closed: Extensible without modification
- Liskov Substitution: Interfaces are properly implemented
- Interface Segregation: Small, focused interfaces
- Dependency Inversion: Depends on abstractions, not concretions
📊 Developer Experience
- Simple API similar to Dio/HTTP
- Comprehensive logging with different levels
- Extensive customization options
- Type-safe responses with custom parsers
Getting Started #
Add this package to your pubspec.yaml:
dependencies:
flutter_network_client: ^1.0.0
Then run:
flutter pub get
Quick Start #
Basic Usage (No Authentication) #
import 'package:flutter_network_client/flutter_network_client.dart';
// Create a simple client
final client = FlutterNetworkClient(
baseUrl: 'https://api.example.com',
enableRequestLogging: true,
);
// Make requests
final response = await client.get<Map<String, dynamic>>(
'/users',
useToken: false, // Disable token for this request
parser: (data) => data as Map<String, dynamic>,
);
if (response.isSuccess) {
print('Data: ${response.data}');
} else {
print('Error: ${response.error}');
}
// POST request
final postResponse = await client.post<Map<String, dynamic>>(
'/users',
body: {'name': 'John', 'email': 'john@example.com'},
useToken: false,
parser: (data) => data as Map<String, dynamic>,
);
client.dispose();
With Built-in Token Management #
// Create client with automatic token management
final client = FlutterNetworkClient.withTokens(
baseUrl: 'https://api.example.com',
tokenConfig: const TokenConfig.bearer(), // Authorization: Bearer {token}
customRefreshHandler: () async {
// Your token refresh logic here
final response = await http.post('/auth/refresh');
return response.data['access_token'];
},
);
// Requests will automatically include tokens
final response = await client.get<List<dynamic>>(
'/protected-data',
parser: (data) => data as List<dynamic>,
);
// Override token usage per request
final publicResponse = await client.get<Map<String, dynamic>>(
'/public-data',
useToken: false, // This request won't include token
parser: (data) => data as Map<String, dynamic>,
);
client.dispose();
With Custom Token Providers #
// Create client with custom token functions
final client = FlutterNetworkClient.withCustomTokens(
baseUrl: 'https://api.example.com',
getToken: () async {
// Your custom logic to get access token
return await SecureStorage.read('access_token');
},
getRefreshToken: () async {
// Your custom logic to get refresh token
return await SecureStorage.read('refresh_token');
},
refreshToken: () async {
// Your custom token refresh logic
final refreshToken = await SecureStorage.read('refresh_token');
final response = await http.post('/auth/refresh',
body: {'refresh_token': refreshToken});
final newToken = response.data['access_token'];
await SecureStorage.write('access_token', newToken);
return newToken;
},
tokenConfig: const TokenConfig.customHeader('X-API-Key'), // Custom header
);
client.dispose();
File Upload Support #
The package supports file uploads using multipart form data:
Single File Upload #
import 'dart:io';
// Create multipart file
final imageFile = MultipartFile.fromFile(
File('path/to/image.jpg'),
field: 'image',
filename: 'profile.jpg',
contentType: 'image/jpeg',
);
// Upload single file
final response = await client.uploadFile<Map<String, dynamic>>(
'/upload',
imageFile,
fields: {
'user_id': '123',
'description': 'Profile picture',
},
parser: (data) => data as Map<String, dynamic>,
);
Multiple Files Upload #
// Create multiple files
final files = [
MultipartFile.fromFile(File('image1.jpg'), field: 'images'),
MultipartFile.fromFile(File('image2.png'), field: 'images'),
];
// Upload multiple files
final response = await client.uploadFiles<Map<String, dynamic>>(
'/upload-multiple',
files,
fields: {'album': 'vacation'},
parser: (data) => data as Map<String, dynamic>,
);
Upload from Bytes #
import 'dart:typed_data';
// Upload from bytes (useful for camera/gallery images)
final imageBytes = Uint8List.fromList([/* image bytes */]);
final multipartFile = MultipartFile.fromBytes(
imageBytes,
field: 'image',
filename: 'camera_photo.jpg',
contentType: 'image/jpeg',
);
final response = await client.uploadFile('/upload', multipartFile);
Custom Form Data #
// Create form data with mixed content
final formData = FormData();
formData.addField('title', 'My Photo');
formData.addField('category', 'nature');
formData.addFile(MultipartFile.fromFile(File('photo.jpg'), field: 'photo'));
formData.addFile(MultipartFile.fromFile(File('thumb.jpg'), field: 'thumbnail'));
final response = await client.uploadFormData<Map<String, dynamic>>(
'/upload-form',
formData,
parser: (data) => data as Map<String, dynamic>,
);
Advanced Usage #
Custom Configuration #
final client = FlutterNetworkClient(
baseUrl: 'https://api.example.com',
defaultHeaders: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-App-Version': '1.0.0',
},
defaultTimeout: const Duration(seconds: 30),
maxRetries: 3,
retryStatusCodes: [408, 502, 503, 504, 429],
enableRequestLogging: true,
enableResponseLogging: true,
rethrowExceptions: false, // If true, exceptions are rethrown instead of returning HttpResponse.error
);
Per-Request Customization #
// Customize individual requests
final response = await client.get<Map<String, dynamic>>(
'/users',
headers: {'X-Custom-Header': 'value'}, // Additional headers
queryParameters: {'page': 1, 'limit': 20}, // Query params
timeout: const Duration(seconds: 60), // Custom timeout
useToken: true, // Enable/disable token for this request
maxRetries: 5, // Custom retry count
retryStatusCodes: [500, 502, 503], // Custom retry conditions
parser: (data) => data as Map<String, dynamic>, // Custom parser
);
Token Configuration Options #
// Bearer token (default)
const TokenConfig.bearer() // Authorization: Bearer {token}
// Token header
const TokenConfig.token() // Authorization: Token {token}
// Custom header
const TokenConfig.customHeader('X-API-Key') // X-API-Key: {token}
// Query parameter
const TokenConfig.queryParameter('api_key') // ?api_key={token}
Error Handling #
try {
final response = await client.get('/users');
// Handle success
} on NoInternetException catch (e) {
// Handle network connectivity issues
print('Network error: ${e.message}');
} on UnauthorizedException catch (e) {
// Handle authentication errors
print('Auth error: ${e.message}');
} on NotFoundException catch (e) {
// Handle 404 errors
print('Not found: ${e.message}');
} on ServerException catch (e) {
// Handle server errors
print('Server error: ${e.message}');
} on NetworkException catch (e) {
// Handle any network exception
print('Network error: ${e.message} (${e.statusCode})');
}
Response Transformation #
// Transform response data
final response = await client.get<List<User>>(
'/users',
parser: (data) {
final list = data as List<dynamic>;
return list.map((item) => User.fromJson(item)).toList();
},
);
// Or transform after receiving
final stringResponse = response.transform<String>((users) {
return users.map((u) => u.name).join(', ');
});
Architecture #
This package follows clean architecture principles and SOLID design patterns:
lib/
├── core/ # Core layer (entities, interfaces, exceptions)
│ ├── entities/ # Core business entities
│ ├── interfaces/ # Abstract interfaces (Dependency Inversion)
│ ├── exceptions/ # Custom exceptions
│ └── enums/ # Enumerations
├── domain/ # Domain layer (business logic)
│ ├── repositories/ # Repository interfaces
│ └── usecases/ # Business use cases
├── data/ # Data layer (implementations)
│ ├── datasources/ # Data source implementations
│ └── repositories/ # Repository implementations
└── presentation/ # Presentation layer (public API)
└── flutter_network_client.dart # Main client facade
SOLID Principles Applied #
- Single Responsibility: Each class has one reason to change
- Open/Closed: Open for extension, closed for modification
- Liskov Substitution: Interfaces are properly implemented
- Interface Segregation: Small, focused interfaces
- Dependency Inversion: Depends on abstractions, not concretions
API Reference #
FlutterNetworkClient #
The main HTTP client class with support for all HTTP methods.
Factory Constructors
FlutterNetworkClient()- Basic client without token managementFlutterNetworkClient.withTokens()- Client with built-in token managementFlutterNetworkClient.withCustomTokens()- Client with custom token providers
Methods
get<T>()- Make GET requestpost<T>()- Make POST requestput<T>()- Make PUT requestdelete<T>()- Make DELETE requestpatch<T>()- Make PATCH requesthead()- Make HEAD requestrequest<T>()- Make custom requestdispose()- Clean up resources
HttpResponse #
Response wrapper with enhanced features.
Properties
isSuccess- Whether the request was successfuldata- Response data (if successful)error- Error message (if failed)statusCode- HTTP status codeduration- Request durationperformanceCategory- Performance category (Excellent, Good, Fair, Slow)headers- Response headersrawBody- Raw response body
Methods
transform<R>()- Transform response data to different type
TokenConfig #
Configuration for token handling.
Factory Constructors
TokenConfig.bearer()- Bearer token in Authorization headerTokenConfig.token()- Token in Authorization headerTokenConfig.customHeader(name)- Custom headerTokenConfig.queryParameter(name)- Query parameter
Error Handling #
The client supports two error handling modes controlled by the rethrowExceptions parameter:
Default Mode (rethrowExceptions: false)
- Returns
HttpResponse.errorwith error details - Includes status code, error message, and response body
- Safe for UI applications that need to handle errors gracefully
final response = await client.get('/api/data');
if (!response.isSuccess) {
print('Error: ${response.error}');
print('Status: ${response.statusCode}');
print('Body: ${response.rawBody}');
}
Exception Mode (rethrowExceptions: true)
- Throws exceptions that can be caught with try-catch
- Useful for applications that prefer exception-based error handling
- All exceptions include response body in the
datafield
try {
final response = await client.get('/api/data');
// Handle success
} catch (e) {
if (e is NetworkException) {
print('Error: ${e.message}');
print('Status: ${e.statusCode}');
print('Response body: ${e.data}');
}
}
Exception Types #
NoInternetException- Network connectivity issuesRequestTimeoutException- Request timeoutUnauthorizedException- Authentication required (401)ForbiddenException- Access forbidden (403)NotFoundException- Resource not found (404)ServerException- Server error (500)BadRequestException- Bad request (400)ValidationException- Validation failed (422)RateLimitException- Rate limit exceeded (429)- And more...
All exceptions now include:
message- Error descriptionstatusCode- HTTP status code (if applicable)data- Response body contentheaders- Response headers
Contributing #
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
License #
This project is licensed under the MIT License - see the LICENSE file for details.
Support #
For support, please open an issue on our GitHub repository or contact us at support@example.com.