flutter_network_client 1.0.1 copy "flutter_network_client: ^1.0.1" to clipboard
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 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
160
points
39
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

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

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, http, http_parser, logger, shared_preferences

More

Packages that depend on flutter_network_client