cyber_req 2.0.12+1 copy "cyber_req: ^2.0.12+1" to clipboard
cyber_req: ^2.0.12+1 copied to clipboard

A flexible Flutter/Dart API client for backends with dynamic headers, secure token management, and comprehensive callbacks.

cyber_req #

A flexible and robust API client for Laravel backends, designed to streamline your Flutter application's network interactions. It offers dynamic header management, secure bearer token handling with flutter_secure_storage, and comprehensive callback mechanisms for success, failure, and unauthorized access.

Features #

  • Robust JSON Body Handling: Ensures correct JSON encoding for complex data structures (including arrays of objects) in POST and PUT requests, preventing server-side validation issues.
  • Dynamic Headers: Effortlessly add or override HTTP headers for individual requests, or set global default headers for consistent API communication.
  • Secure Bearer Token Management: Seamlessly integrate bearer tokens into your requests, with built-in support for fetching and storing tokens securely using flutter_secure_storage.
  • Laravel Backend Optimization: Crafted with common Laravel API patterns in mind, ensuring smooth handling of JSON responses and efficient error propagation.
  • Granular Callbacks: Utilize onSuccess, onFailure, and onUnauthorized callbacks to gain fine-grained control over API responses and error handling logic.
  • Custom Exception Handling: Benefit from dedicated ApiException and UnauthorizedException classes for robust and predictable error management.
  • Comprehensive HTTP Method Support: Perform POST, GET, PUT, and DELETE requests with ease.
  • Intelligent Network Error Handling: Automatically catches and reports common network issues such as lack of internet connectivity or invalid response formats.
  • Automatic Request/Response Logging: Optionally log comprehensive request details (URL, method, headers, payload, query parameters) and full response details (URL, status code, body) directly to the console for easy debugging using dart:developer's log function. This is controlled by the autoLogResponse parameter.

Installation #

Add cyber_req to your pubspec.yaml file:

dependencies:
  cyber_req: ^2.0.12 # Use the latest version

After adding the dependencies, run flutter pub get in your terminal to fetch the packages.

Quick Start #

To see cyber_req in action with various use cases, including basic GET, GET with bearer token (simulating unauthorized), and POST requests with callbacks, refer to the example/main.dart file in the project's example directory.

To run the example:

  1. Navigate to the example directory:
    cd example
    
  2. Get the dependencies:
    flutter pub get
    
  3. Run the example application:
    flutter run
    

This example demonstrates how to use ApiService with onSuccess, onFailure, bearerToken, and onUnauthorized handlers.

Usage #

Initialization of ApiService #

The ApiService is the core of cyber_req. You initialize it with your API's base URL and can configure it further with various optional parameters.

import 'package:cyber_req/cyber_req.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart' as http; // Only if you need a custom http.Client

final storage = FlutterSecureStorage();

// Example unauthorized handler: This function will be called when a 401 Unauthorized response is received.
Future<void> myUnauthorizedHandler() async {
  print('Authentication failed! User needs to re-authenticate.');
  await storage.delete(key: 'token'); // Clear any invalid token
  // Implement navigation to login screen or token refresh logic here.
}

final ApiService apiService = ApiService(
  baseUrl: 'https://your-laravel-backend.com/api', // **Required**: Your API's base URL.
  bearerToken: 'your_static_bearer_token_if_any', // Optional: A static bearer token to be used for all requests.
                                                  // Prefer `useStorageToken` for dynamic tokens.
  httpClient: http.Client(), // Optional: Provide a custom http.Client instance. Useful for testing or custom configurations.
  onUnauthorized: myUnauthorizedHandler, // Optional: Callback function for 401 Unauthorized responses.
  defaultHeaders: { // Optional: Headers that will be sent with every request.
    'Content-Type': 'application/json',
    'X-App-Version': '1.0.0',
  },
  autoLogResponse: true, // Optional: Set to `false` to disable automatic logging of requests and responses. Defaults to `true`.
);

Making Requests #

ApiService provides methods for common HTTP verbs: post, get, put, and delete. Each method returns a Future<Map<String, dynamic>> representing the JSON response from your API.

Common Request Parameters

All request methods (post, get, put, delete) share several optional parameters for fine-grained control:

  • data: (Map<String, dynamic>) For POST and PUT requests, this map will be JSON encoded and sent as the request body.
  • queryParams: (Map<String, dynamic>) For GET requests, these parameters will be appended to the URL as query strings.
  • extraHeaders: (Map<String, String>) Headers specific to this request. These will override defaultHeaders if there are conflicts.
  • bearerToken: (String?) A bearer token to use for this specific request. If provided, it takes precedence over the ApiService's bearerToken and any token from flutter_secure_storage.
  • useStorageToken: (bool, defaults to false) If true, cyber_req will attempt to read a token from flutter_secure_storage under the key 'token' and use it as the Authorization header.
  • onSuccess: (SuccessCallback?) A callback function void Function(Map<String, dynamic> data, {int? statusCode}) that is executed when the request is successful (HTTP status 2xx) and the response body is successfully decoded. The statusCode is optional and can be used to get the HTTP status code.
  • onFailure: (FailureCallback?) A callback function void Function(ApiException error, {int? statusCode}) that is executed when the request fails (non-2xx status, network error, or invalid response format).
  • allowedStatusCodes: (List<int>?) A list of additional HTTP status codes (beyond 2xx) that should be considered successful. For example, if your API returns 201 Created or 204 No Content for success, you might include them here.
  • timeout: (Duration?) An optional duration to set a custom timeout for a specific request. If not provided, the ApiService's default timeout (30 seconds) will be used.

POST Request Example

Future<void> createNewUser(String name, String email, String password) async {
  try {
    final response = await apiService.post(
      'register', // Your API endpoint
      data: {
        'name': name,
        'email': email,
        'password': password,
      },
      extraHeaders: {
        'X-Request-ID': 'unique-id-123', // Example of an extra header
      },
      // No useStorageToken here, as this might be a registration endpoint
      onSuccess: (data) {
        print('User registered successfully: $data');
        // Save the token if returned by the API
        if (data.containsKey('token')) {
          storage.write(key: 'token', value: data['token']);
          print('Token saved to secure storage.');
        }
      },
      onFailure: (error) {
        print('Registration failed: ${error.message}');
        // Handle specific error messages from the backend
      },
    );
    print('Raw POST response: $response');
  } on ApiException catch (e) {
    print('API Exception during POST: ${e.message} (Status: ${e.statusCode})');
  } on UnauthorizedException {
    print('Unauthorized access during POST. This should not happen for registration.');
  } catch (e) {
    print('An unexpected error occurred during POST: $e');
  }
}

GET Request Example

Future<void> fetchUserProfile(String userId) async {
  try {
    final response = await apiService.get(
      'users/$userId', // Endpoint with path parameter
      queryParams: {
        'include_posts': 'true', // Example query parameter
        'limit': '5',
      },
      useStorageToken: true, // Use the token from FlutterSecureStorage
      onSuccess: (data) {
        print('User profile fetched: ${data['user']['name']}');
      },
    );
    print('Raw GET response: $response');
  } on ApiException catch (e) {
    print('API Exception during GET: ${e.message} (Status: ${e.statusCode})');
  } catch (e) {
    print('An unexpected error occurred during GET: $e');
  }
}

PUT Request Example

Future<void> updateProduct(String productId, Map<String, dynamic> updates) async {
  try {
    final response = await apiService.put(
      'products/$productId',
      data: updates,
      useStorageToken: true,
      onSuccess: (data) {
        print('Product updated successfully: $data');
      },
    );
    print('Raw PUT response: $response');
  } on ApiException catch (e) {
    print('API Exception during PUT: ${e.message} (Status: ${e.statusCode})');
  } catch (e) {
    print('An unexpected error occurred during PUT: $e');
  }
}

DELETE Request Example

Future<void> removeComment(String commentId) async {
  try {
    final response = await apiService.delete(
      'comments/$commentId',
      useStorageToken: true,
      allowedStatusCodes: [204], // API might return 204 No Content for successful deletion
      onSuccess: (data) {
        print('Comment deleted successfully.');
      },
    );
    print('Raw DELETE response: $response');
  } on ApiException catch (e) {
    print('API Exception during DELETE: ${e.message} (Status: ${e.statusCode})');
  } catch (e) {
    print('An unexpected error occurred during DELETE: $e');
  }
}

Multipart POST Request Example

For uploading files, cyber_req provides the postMultipart method. This method is designed to handle multipart/form-data requests, allowing you to send files along with other form fields.

First, define MultipartFile objects for each file you want to upload:

import 'dart:io';
import 'package:cyber_req/cyber_req.dart'; // Ensure this import is present


Future<void> uploadProfilePicture(String userId, File imageFile) async {
  try {
    final response = await apiService.postMultipart(
      'users/$userId/profile-picture', // Your API endpoint for file upload
      files: [
        MultipartFile(
          field: 'profile_picture', // The field name expected by your backend for the file
          file: imageFile,
          filename: 'profile.jpg', // Optional: specify a filename, otherwise it's inferred
        ),
      ],
      fields: {
        'description': 'User profile picture upload', // Optional: additional text fields
        'user_id': userId,
      },
      useStorageToken: true, // Use the token from FlutterSecureStorage
      onSuccess: (data) {
        print('Profile picture uploaded successfully: $data');
      },
      onFailure: (error) {
        print('Profile picture upload failed: ${error.message}');
      },
    );
    print('Raw Multipart POST response: $response');
  } on ApiException catch (e) {
    print('API Exception during Multipart POST: ${e.message} (Status: ${e.statusCode})');
  } catch (e) {
    print('An unexpected error occurred during Multipart POST: $e');
  }
}

MultipartFile Class:

The MultipartFile class is a simple data structure used to define the files you want to upload:

class MultipartFile {
  final String field; // The name of the form field for this file (e.g., 'image', 'document')
  final File file;    // The actual file to be uploaded
  final String? filename; // Optional: The filename to send to the server. If null, inferred from file path.

  MultipartFile({
    required this.field,
    required this.file,
    this.filename,
  });
}

Token Management with flutter_secure_storage #

cyber_req integrates seamlessly with flutter_secure_storage for secure token persistence.

Saving a Token:

After a successful login or token refresh, save the token:

import 'package:flutter_secure_storage/flutter_secure_storage.dart';

final storage = FlutterSecureStorage();

Future<void> saveAuthToken(String token) async {
  await storage.write(key: 'token', value: token);
  print('Authentication token saved securely.');
}

Using a Stored Token in Requests:

Set useStorageToken: true in your request methods:

// This will automatically fetch the token from secure storage (key: 'token')
// and include it in the Authorization header.
final response = await apiService.get('protected-data', useStorageToken: true);

Clearing a Token (e.g., on Logout or Unauthorized):

Future<void> clearAuthToken() async {
  await storage.delete(key: 'token');
  print('Authentication token cleared from secure storage.');
}

Error Handling #

cyber_req provides robust error handling through custom exceptions. It's crucial to wrap your API calls in try-catch blocks to manage different error scenarios gracefully.

  • ApiException: The base exception for all API-related errors. It contains a message (from the backend or a generic error) and an optional statusCode.
  • UnauthorizedException: A specific subclass of ApiException that is thrown when an HTTP 401 (Unauthorized) status code is received. This is particularly useful for triggering re-authentication flows.

cyber_req also provides intelligent handling for common network and format errors, throwing specific exceptions for easier debugging and user feedback:

  • SocketException: Thrown for network connectivity issues (e.g., no internet connection).
  • HttpException: Thrown for general HTTP errors during communication with the server.
  • FormatException: Thrown when the server response is not a valid JSON format.
try {
  // Attempt to fetch data that requires authentication
  final data = await apiService.get('user/dashboard', useStorageToken: true);
  print('Dashboard data: $data');
} on UnauthorizedException {
  // Handle 401 Unauthorized specifically
  print('Access denied. Your session may have expired. Please log in again.');
  // Trigger your app's logout or token refresh flow
  await storage.delete(key: 'token'); // Clear invalid token
  // Navigator.pushReplacementNamed(context, '/login');
} on ApiException catch (e) {
  // Handle other API errors (e.g., 400 Bad Request, 404 Not Found, 500 Internal Server Error)
  print('An API error occurred: ${e.message} (Status Code: ${e.statusCode})');
  if (e.statusCode == 404) {
    print('Resource not found.');
  } else if (e.statusCode == 403) {
    print('Permission denied.');
  }
  // Display an error message to the user
} on SocketException {
  // Handle network connectivity issues (e.g., no internet)
  print('Network error: Please check your internet connection.');
} on HttpException {
  // Handle general HTTP errors during communication
  print('HTTP error: Failed to communicate with server.');
} on FormatException {
  // Handle cases where the server response is not valid JSON
  print('Invalid response format from server.');
} catch (e) {
  // Catch any other unexpected errors
  print('An unexpected error occurred: $e');
}

Disposing the Client #

It's a good practice to dispose of the ApiService's internal http.Client when it's no longer needed to prevent memory leaks and ensure resources are properly released. This is especially important in stateful widgets.

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  late final ApiService apiService;

  @override
  void initState() {
    super.initState();
    apiService = ApiService(baseUrl: 'https://api.example.com');
  }

  @override
  void dispose() {
    apiService.dispose(); // Call dispose when the widget is removed from the tree
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // ... your widget build method
    return Container();
  }
}

API Reference (Brief) #

  • ApiService({required String baseUrl, String? bearerToken, http.Client? httpClient, UnauthorizedHandler? onUnauthorized, Map<String, String>? defaultHeaders, bool autoLogResponse = true, Duration timeout = const Duration(seconds: 30)}): Constructor for the API service.
  • Future<Map<String, dynamic>> post(String endpoint, {Map<String, dynamic>? data, Map<String, String>? extraHeaders, String? bearerToken, bool useStorageToken, SuccessCallback? onSuccess, FailureCallback? onFailure, List<int>? allowedStatusCodes, Duration? timeout}): Sends a POST request.
  • Future<Map<String, dynamic>> get(String endpoint, {Map<String, dynamic>? queryParams, Map<String, String>? extraHeaders, String? bearerToken, bool useStorageToken, SuccessCallback? onSuccess, FailureCallback? onFailure, List<int>? allowedStatusCodes, Duration? timeout}): Sends a GET request.
  • Future<Map<String, dynamic>> put(String endpoint, {Map<String, dynamic>? data, Map<String, String>? extraHeaders, String? bearerToken, bool useStorageToken, SuccessCallback? onSuccess, FailureCallback? onFailure, List<int>? allowedStatusCodes, Duration? timeout}): Sends a PUT request.
  • Future<Map<String, dynamic>> delete(String endpoint, {Map<String, String>? extraHeaders, String? bearerToken, bool useStorageToken, SuccessCallback? onSuccess, FailureCallback? onFailure, List<int>? allowedStatusCodes, Duration? timeout}): Sends a DELETE request.
  • Future<Map<String, dynamic>> postMultipart(String endpoint, {required List<MultipartFile> files, Map<String, String>? fields, Map<String, String>? extraHeaders, String? bearerToken, bool useStorageToken, SuccessCallback? onSuccess, FailureCallback? onFailure, List<int>? allowedStatusCodes, Duration? timeout}): Sends a multipart POST request with files and optional fields.
  • MultipartFile({required String field, required File file, String? filename}): Constructor for defining a file to be uploaded in a multipart request.
  • void dispose(): Closes the internal HTTP client.

Contributing #

Contributions are welcome! If you find a bug or have a feature request, please open an issue on the GitHub repository.

License #

This project is licensed under the MIT License.

Support and Donations #

If you find this package useful and would like to support its continued development, consider a donation!

WhatsApp: Chat with me on WhatsApp

Email: eminibest@gmail.com


cyber_req is maintained by cyberwizard-dev.

2
likes
0
points
39
downloads

Publisher

unverified uploader

Weekly Downloads

A flexible Flutter/Dart API client for backends with dynamic headers, secure token management, and comprehensive callbacks.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter_secure_storage, http

More

Packages that depend on cyber_req