minimal_rest_http 1.0.0
minimal_rest_http: ^1.0.0 copied to clipboard
A minimal, dynamic, and configurable REST HTTP client for Flutter applications with built-in error handling, retry logic, and flexible endpoint management.
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