cyber_req 1.0.3
cyber_req: ^1.0.3 copied to clipboard
A flexible API client for Laravel backends with dynamic headers, bearer token support, and callbacks.
cyber_req - A Robust HTTP Client for Dart/Flutter #
cyber_req is a lightweight, yet powerful and robust HTTP client for Dart and Flutter applications. Built on top of the official http package, it streamlines your API interactions by providing a structured approach to making requests, handling responses, and managing common scenarios like authentication and error handling.
Table of Contents #
Features #
cyber_req offers a comprehensive set of features designed to make your network layer clean and efficient:
- Simplified API Calls: Intuitive methods (
post,get,put,delete) for all standard HTTP verbs. - Automatic JSON Handling: Effortlessly sends JSON request bodies and parses JSON responses.
- Flexible Headers: Easily set global default headers and override them with request-specific headers.
- Bearer Token Support: Built-in mechanism for automatically attaching bearer tokens to authenticated requests.
- Comprehensive Error Handling: Dedicated custom exceptions (
ApiException,UnauthorizedException) for clear error identification and handling, covering network issues, server responses, and malformed data. - Graceful Unauthorized Handling: A powerful
onUnauthorizedcallback to manage 401 responses, perfect for token refreshing, re-authentication, or user logout flows. - Success and Failure Callbacks: Optional
onSuccessandonFailurecallbacks for immediate, request-specific response handling. - Customizable Status Code Acceptance: Define a list of
allowedStatusCodesto treat non-2xx responses as successful for specific endpoints.
Installation #
To start using cyber_req, add it to your project's pubspec.yaml file:
dependencies:
cyber_req: ^1.0.0 # Use the latest version available on pub.dev
http: ^1.0.0 # Ensure you have the http package as well, cyber_req depends on it
After adding the dependency, run flutter pub get (for Flutter projects) or dart pub get (for pure Dart projects) in your terminal to fetch the package.
Usage #
Basic Initialization #
Initialize ApiService with your base URL and optionally configure default headers, a bearer token, and an unauthorized handler.
import 'package:cyber_req/cyber_req.dart';
import 'package:http/http.dart' as http; // Required if you pass a custom http.Client
// Typically, you'd initialize this once, perhaps in a service locator or provider.
final ApiService apiService = ApiService(
baseUrl: 'https://api.yourapp.com/v1',
// Optional: A global bearer token that will be attached to all requests
// unless overridden by a request-specific token.
bearerToken: 'your_global_auth_token_here',
// Optional: A custom http.Client if you need specific configurations (e.g., for testing)
// httpClient: http.Client(),
// Optional: A callback that fires when a 401 Unauthorized response is received.
// Ideal for refreshing tokens or redirecting to a login screen.
onUnauthorized: () async {
print('401 Unauthorized: Attempting to refresh token or log out...');
// Example: Call your authentication service to refresh the token
// await AuthService.instance.refreshToken();
// Or navigate to login:
// Navigator.of(context).pushReplacementNamed('/login');
},
// Optional: Headers that will be sent with every request by default.
defaultHeaders: {
'X-App-Version': '1.0.0',
'Accept-Language': 'en-US',
},
);
Making a POST Request #
Send data to your API using the post method.
Future<void> createNewUser() async {
try {
final Map<String, dynamic> userData = {
'name': 'Alice Smith',
'email': 'alice.smith@example.com',
'password': 'securepassword123',
};
final response = await apiService.post(
'users', // Endpoint relative to baseUrl
data: userData,
// Optional: Request-specific headers
extraHeaders: {'X-Request-ID': 'unique-post-id-123'},
// Optional: Request-specific bearer token (overrides global one)
bearerToken: 'specific_post_token',
// Optional: Callback for successful response
onSuccess: (data) {
print('User created successfully! Response: $data');
},
// Optional: Callback for API errors (ApiException)
onFailure: (error) {
print('Failed to create user: ${error.message} (Status: ${error.statusCode})');
},
);
print('Full POST response data: $response');
} on UnauthorizedException {
print('Authentication required to create user.');
} on ApiException catch (e) {
print('API Error creating user: ${e.message} (Status: ${e.statusCode})');
} catch (e) {
print('An unexpected error occurred: $e');
}
}
Making a GET Request with Query Parameters #
Retrieve data using the get method, with support for query parameters.
Future<void> fetchProducts(String category, int limit) async {
try {
final response = await apiService.get(
'products',
queryParams: {
'category': category,
'limit': limit.toString(),
'sort_by': 'price_asc',
},
extraHeaders: {'Cache-Control': 'no-cache'},
);
print('Fetched products: $response');
} on ApiException catch (e) {
print('Error fetching products: ${e.message} (Status: ${e.statusCode})');
}
}
Making PUT and DELETE Requests #
Similar to POST and GET, put and delete methods are available.
Future<void> updateProduct(String productId, Map<String, dynamic> updates) async {
try {
final response = await apiService.put(
'products/$productId',
data: updates,
);
print('Product $productId updated: $response');
} on ApiException catch (e) {
print('Error updating product: ${e.message}');
}
}
Future<void> deleteProduct(String productId) async {
try {
final response = await apiService.delete(
'products/$productId',
onSuccess: (data) {
print('Product $productId deleted successfully.');
},
);
print('Delete response: $response');
} on ApiException catch (e) {
print('Error deleting product: ${e.message}');
}
}
Handling Custom Status Codes #
Sometimes, an API might return a non-2xx status code that you want to treat as a success (e.g., 204 No Content, or a custom 409 Conflict that indicates a known business logic state). Use allowedStatusCodes for this.
Future<void> submitFormWithCustomConflictHandling() async {
try {
final response = await apiService.post(
'forms/submit',
data: {'field': 'value'},
allowedStatusCodes: [409], // Treat 409 Conflict as a handled success
);
if (response['code'] == 'CONFLICT_ERROR') {
print('Form submission conflict: ${response['message']}');
// Handle the specific conflict logic
} else {
print('Form submitted successfully: $response');
}
} on ApiException catch (e) {
print('Form submission failed: ${e.message} (Status: ${e.statusCode})');
}
}
Disposing the ApiService #
It's good practice to dispose() the ApiService when it's no longer needed (e.g., when a stateful widget is disposed, or your application is shutting down) to close the underlying http.Client and free up resources.
// In a StatefulWidget's dispose method:
// @override
// void dispose() {
// apiService.dispose();
// super.dispose();
// }
Error Handling #
cyber_req provides a robust error handling mechanism through custom exceptions, allowing you to catch and respond to specific issues.
ApiException: This is the base exception for all API-related errors. It includes amessageand an optionalstatusCode.- Thrown for:
- Network issues (
SocketException,HttpException). - Invalid JSON response format (
FormatException). - Server errors (non-2xx status codes not in
allowedStatusCodes). - Unsupported HTTP methods.
- Network issues (
- Thrown for:
UnauthorizedException: A specializedApiExceptionthat is thrown specifically when a 401 Unauthorized status code is received. This allows for distinct handling of authentication failures.
Always wrap your API calls in try-catch blocks to gracefully handle potential errors:
Future<void> fetchSensitiveData() async {
try {
final data = await apiService.get('user/profile');
print('User profile data: $data');
} on UnauthorizedException {
print('Access denied. Please log in again or refresh your session.');
// Example: Navigate to login screen or trigger a re-authentication flow.
} on ApiException catch (e) {
print('API Error fetching profile: ${e.message}');
if (e.statusCode != null) {
print('Status Code: ${e.statusCode}');
// Handle specific HTTP status codes (e.g., 404 Not Found, 500 Internal Server Error)
if (e.statusCode == 404) {
print('Profile not found.');
}
}
} on Exception catch (e) {
print('An unexpected system error occurred: $e');
// Catch any other unexpected Dart/Flutter errors
}
}
API Reference #
ApiService #
The core class for making HTTP requests.
- Constructor:
ApiService({ required String baseUrl, String? bearerToken, http.Client? httpClient, UnauthorizedHandler? onUnauthorized, Map<String, String>? defaultHeaders, }) - Methods:
Future<Map<String, dynamic>> post(String endpoint, {Map<String, dynamic>? data, Map<String, String>? extraHeaders, String? bearerToken, SuccessCallback? onSuccess, FailureCallback? onFailure, List<int>? allowedStatusCodes})Future<Map<String, dynamic>> get(String endpoint, {Map<String, dynamic>? queryParams, Map<String, String>? extraHeaders, String? bearerToken, SuccessCallback? onSuccess, FailureCallback? onFailure, List<int>? allowedStatusCodes})Future<Map<String, dynamic>> put(String endpoint, {Map<String, dynamic>? data, Map<String, String>? extraHeaders, String? bearerToken, SuccessCallback? onSuccess, FailureCallback? onFailure, List<int>? allowedStatusCodes})Future<Map<String, dynamic>> delete(String endpoint, {Map<String, String>? extraHeaders, String? bearerToken, SuccessCallback? onSuccess, FailureCallback? onFailure, List<int>? allowedStatusCodes})void dispose(): Closes the underlyinghttp.Client.
Exceptions #
ApiExceptionclass ApiException implements Exception { final String message; final int? statusCode; ApiException(this.message, [this.statusCode]); }UnauthorizedExceptionclass UnauthorizedException extends ApiException { UnauthorizedException([String message = 'Unauthorized']) : super(message, 401); }
Type Definitions #
UnauthorizedHandler:typedef UnauthorizedHandler = Future<void> Function();SuccessCallback:typedef SuccessCallback = void Function(Map<String, dynamic> data);FailureCallback:typedef FailureCallback = void Function(ApiException error);
Contributing #
Contributions are welcome! If you find a bug or have a feature request, please open an issue on the GitHub repository. If you'd like to contribute code, please fork the repository and submit a pull request.
License #
This project is licensed under the terms of the MIT License.