Minimal REST HTTP Package
A minimal, dynamic, and configurable REST HTTP client for Flutter applications with built-in error handling, retry logic, and flexible endpoint management. Perfect for developers who want a robust, type-safe, and easy-to-use HTTP client with comprehensive features.
✨ Features
- 🚀 Dynamic Configuration: Easy setup with configurable base URLs, timeouts, and settings
- 🔐 Flexible Authentication: Multiple authentication managers (SharedPreferences, Memory, Custom)
- 📍 Endpoint Management: Dynamic endpoint management system for better maintainability
- 🔄 Retry Logic: Built-in retry mechanism for failed requests with configurable attempts
- 🛡️ Error Handling: Comprehensive error handling with optional UI popups
- 📱 Connectivity Check: Automatic internet connectivity verification before requests
- 🎯 Type Safety: Full type safety with generic response handling
- 📊 Logging: Configurable request/response logging for debugging
- 🔧 Extensible: Easy to extend and customize for your specific needs
- 🌐 Any Response Format: Works with any API response structure
- 🎨 Custom UI Integration: Easy integration with your existing popup/dialog systems
- 📁 File Upload: Multipart file upload support
- ⚡ Performance: Optimized for performance with connection pooling
📦 Installation
Add this to your package's pubspec.yaml file:
dependencies:
minimal_rest_http: ^1.0.0
Then run:
flutter pub get
🚀 Quick Start
1. Initialize the API Service
import 'package:minimal_rest_http/api_service.dart';
Future<void> initializeMinRestService() async {
// Create configuration
final config = ApiConfig(
baseUrl: 'https://jsonplaceholder.typicode.com',
timeoutSeconds: 30,
enableLogging: true,
enableErrorPopups: true,
);
// Create auth manager
final authManager = SharedPreferencesAuthManager(
tokenKey: 'access_token',
);
// Create endpoint manager (optional)
final endpointManager = EndpointManager(baseUrl: config.baseUrl);
endpointManager.addEndpoints({
'users': '/users',
'posts': '/posts',
'login': '/auth/login',
});
// Initialize Minimal REST HTTP service
await MinRestService.initialize(
config: config,
authManager: authManager,
endpointManager: endpointManager,
);
}
2. Make API Calls
// Using direct endpoints
final users = await MinRestService.instance.get<List<dynamic>>(
'/users',
fromJson: (data) => data as List<dynamic>,
);
// Using endpoint manager
final posts = await MinRestService.instance.getByKey<List<dynamic>>(
'posts',
fromJson: (data) => data as List<dynamic>,
);
// POST request with authentication
final newUser = await MinRestService.instance.post<Map<String, dynamic>>(
'/users',
body: {
'name': 'John Doe',
'email': 'john@example.com',
},
fromJson: (data) => data as Map<String, dynamic>,
authToken: true,
);
3. Set Up Automatic Context Detection
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: MyHomePage().withApiContext(), // Automatic context detection
);
}
}
⚙️ Configuration
ApiConfig
final config = ApiConfig(
baseUrl: 'https://api.example.com', // Base URL for API
timeoutSeconds: 30, // Request timeout
maxRetryAttempts: 2, // Retry attempts
enableLogging: true, // Enable request/response logging
enableErrorPopups: false, // Enable error popups
defaultHeaders: { // Default headers
'Accept': 'application/json',
},
authTokenKey: 'access_token', // Key for storing auth token
);
Runtime Configuration
final minRestService = MinRestService.instance;
// Change base URL temporarily
await minRestService.withScope((service) async {
return await service.get('/different-endpoint', fromJson: (data) => data);
}, baseUrl: 'https://different-api.com');
// Change timeout temporarily
await minRestService.withScope((service) async {
return await service.get('/slow-endpoint', fromJson: (data) => data);
}, timeoutSeconds: 60);
// Persistent configuration changes
minRestService.withBaseUrl('https://new-api.com');
minRestService.withTimeout(45);
🔐 Authentication
SharedPreferences Auth Manager
final authManager = SharedPreferencesAuthManager(
tokenKey: 'access_token',
);
// Set token
await authManager.setToken('your_jwt_token');
// Get token
final token = await authManager.getToken();
// Clear token
await authManager.clearToken();
// Check if token exists
final hasToken = await authManager.hasToken();
Custom Auth Manager
final authManager = CustomAuthManager(
getTokenFunction: () async => await getTokenFromSecureStorage(),
setTokenFunction: (token) async => await saveTokenToSecureStorage(token),
clearTokenFunction: () async => await clearTokenFromSecureStorage(),
hasTokenFunction: () async => await hasTokenInSecureStorage(),
tokenStream: tokenChangeStream,
);
📍 Endpoint Management
final endpointManager = EndpointManager(baseUrl: 'https://api.example.com');
// Add single endpoint
endpointManager.addEndpoint('users', '/users');
// Add multiple endpoints
endpointManager.addEndpoints({
'users': '/users',
'posts': '/posts',
'comments': '/comments',
'login': '/auth/login',
'profile': '/user/profile',
});
// Use endpoints
final users = await minRestService.getByKey<List<dynamic>>(
'users',
fromJson: (data) => data as List<dynamic>,
);
// Build URLs with parameters
final url = endpointManager.buildUrlWithParams(
'users',
queryParams: {'page': '1', 'limit': '10'},
);
🛡️ Error Handling with AppPopup
The API Service package integrates seamlessly with your existing AppPopup system. When showErrorPopup is true, it will automatically show error popups using your AppPopup system.
Automatic Context Detection (Recommended)
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: MyHomePage().withApiContext(), // Automatic context detection
);
}
}
Using AppPopup System
// API calls will automatically show popups when showErrorPopup is true
Future<void> loadUsers() async {
try {
final response = await MinRestService.instance.getByKey<dynamic>(
'users',
fromJson: (data) => data,
showErrorPopup: true, // This will show your AppPopup on error
);
// Handle response...
} catch (e) {
// Error popup is already shown by the API service
// Handle any additional logic here if needed
}
}
🌐 Handling Any Response Format
The API Service package works with any response format. Here's how to handle different response structures:
Simple Array Response
// API returns: [{"id": 1, "name": "John"}, {"id": 2, "name": "Jane"}]
final response = await MinRestService.instance.get<dynamic>(
'/users',
fromJson: (data) => data,
);
List<Map<String, dynamic>> users;
if (response is List) {
users = response.cast<Map<String, dynamic>>();
} else {
users = [];
}
Wrapped Response
// API returns: {"success": true, "data": [{"id": 1, "name": "John"}]}
final response = await MinRestService.instance.get<dynamic>(
'/users',
fromJson: (data) => data,
);
List<Map<String, dynamic>> users;
if (response is Map<String, dynamic>) {
final data = ResponseUtils.getData(response);
if (data is List) {
users = data.cast<Map<String, dynamic>>();
} else {
users = [];
}
}
// Check if response indicates success
final isSuccess = ResponseUtils.isSuccess(response);
if (!isSuccess) {
final errorMessage = ResponseUtils.getErrorMessage(response);
print('API Error: $errorMessage');
}
Using ResponseUtils
// Extract data from any response format
final data = ResponseUtils.getData(response);
// Check if response indicates success
final isSuccess = ResponseUtils.isSuccess(response);
// Get error message
final errorMessage = ResponseUtils.getErrorMessage(response);
// Check for pagination
if (ResponseUtils.hasPagination(response)) {
final pagination = ResponseUtils.getPaginationInfo(response);
print('Current page: ${pagination?['currentPage']}');
}
📡 HTTP Methods
GET Request
final data = await minRestService.get<Map<String, dynamic>>(
'/endpoint',
fromJson: (data) => data as Map<String, dynamic>,
authToken: true,
queryParams: {'param1': 'value1'},
);
POST Request
final result = await minRestService.post<Map<String, dynamic>>(
'/endpoint',
body: {'key': 'value'},
fromJson: (data) => data as Map<String, dynamic>,
authToken: true,
);
Multipart POST (File Upload)
final result = await minRestService.postMultipart<Map<String, dynamic>>(
endpoint: '/upload',
fromJson: (data) => data as Map<String, dynamic>,
fileKey: 'file',
filePath: '/path/to/file.jpg',
fields: {'description': 'My file'},
authToken: true,
);
PUT Request
final result = await minRestService.put<Map<String, dynamic>>(
'/endpoint/1',
{'key': 'updated_value'},
(data) => data as Map<String, dynamic>,
authToken: true,
);
DELETE Request
final result = await minRestService.delete<Map<String, dynamic>>(
'/endpoint/1',
(data) => data as Map<String, dynamic>,
authToken: true,
);
🔧 Advanced Usage
Custom Configuration per Request
// Use different base URL for specific request
await minRestService.withScope((service) async {
return await service.get('/external-api', fromJson: (data) => data);
}, baseUrl: 'https://external-api.com');
// Use different timeout for specific request
await minRestService.withScope((service) async {
return await service.get('/slow-endpoint', fromJson: (data) => data);
}, timeoutSeconds: 120);
Connectivity Check
// Check connectivity
final hasConnection = await ConnectivityHelper.hasInternetConnection();
// Get detailed connectivity status
final status = await ConnectivityHelper.getConnectivityStatus();
print('Connection type: ${status['connectionTypes']}');
// Listen to connectivity changes
ConnectivityHelper.connectivityStream.listen((results) {
print('Connectivity changed: $results');
});
📱 Example Project
Check out the example/ directory for a complete working example that demonstrates:
- Basic API service setup
- Authentication handling
- Error handling with popups
- Different response formats
- File upload functionality
🚀 Migration from Hardcoded API Services
Before (Hardcoded)
class UrlHelper {
static const String BaseUrl = "https://api.example.com";
static const String users = "/users";
static const String posts = "/posts";
}
// Usage
final response = await http.get(Uri.parse('${UrlHelper.BaseUrl}${UrlHelper.users}'));
After (Dynamic)
// Setup
final endpointManager = EndpointManager(baseUrl: 'https://api.example.com');
endpointManager.addEndpoints({
'users': '/users',
'posts': '/posts',
});
// Usage
final response = await apiService.getByKey<Map<String, dynamic>>(
'users',
fromJson: (data) => data as Map<String, dynamic>,
);
🎯 Best Practices
- Initialize Early: Initialize the API service in your app's main function
- Use Endpoint Manager: Define all endpoints in one place for better maintainability
- Handle Errors: Always handle errors appropriately for better user experience
- Type Safety: Use proper type definitions for your response models
- Configuration: Use different configurations for different environments (dev, staging, prod)
- Logging: Enable logging in development, disable in production
- Timeouts: Set appropriate timeouts based on your API's response times
📚 Documentation
- Integration Guide - How to integrate with your existing popup system
- Migration Guide - Step-by-step migration from hardcoded API services
- Changelog - Version history and changes
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
👨💻 Author
MD Tangim Haque - @imtangim
- Website: imtangimhere.web.app
- LinkedIn: in/tangimhere
- Instagram: _mr_tangim
⭐ Show Your Support
Give a ⭐️ if this project helped you!
🐛 Report Issues
If you find any issues or have suggestions, please open an issue.
Made with ❤️ by MD Tangim Haque
Libraries
- api_service
- Minimal REST HTTP Package
- exceptions/api_exception
- exceptions/network_exception
- exceptions/timeout_exception
- popup/popup_entenstion