flutter_network_client 1.0.0 copy "flutter_network_client: ^1.0.0" to clipboard
flutter_network_client: ^1.0.0 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 management
  • FlutterNetworkClient.withTokens() - Client with built-in token management
  • FlutterNetworkClient.withCustomTokens() - Client with custom token providers

Methods

  • get<T>() - Make GET request
  • post<T>() - Make POST request
  • put<T>() - Make PUT request
  • delete<T>() - Make DELETE request
  • patch<T>() - Make PATCH request
  • head() - Make HEAD request
  • request<T>() - Make custom request
  • dispose() - Clean up resources

HttpResponse #

Response wrapper with enhanced features.

Properties

  • isSuccess - Whether the request was successful
  • data - Response data (if successful)
  • error - Error message (if failed)
  • statusCode - HTTP status code
  • duration - Request duration
  • performanceCategory - Performance category (Excellent, Good, Fair, Slow)
  • headers - Response headers
  • rawBody - 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 header
  • TokenConfig.token() - Token in Authorization header
  • TokenConfig.customHeader(name) - Custom header
  • TokenConfig.queryParameter(name) - Query parameter

Error Handling #

The client supports two error handling modes controlled by the rethrowExceptions parameter:

Default Mode (rethrowExceptions: false)

  • Returns HttpResponse.error with 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 data field
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 issues
  • RequestTimeoutException - Request timeout
  • UnauthorizedException - 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 description
  • statusCode - HTTP status code (if applicable)
  • data - Response body content
  • headers - 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.

1
likes
120
points
39
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Advanced HTTP client for Flutter with authentication, retry logic, and clean architecture.

License

MIT (license)

Dependencies

flutter, http, http_parser, logger, shared_preferences

More

Packages that depend on flutter_network_client