dio_flow 1.1.10 copy "dio_flow: ^1.1.10" to clipboard
dio_flow: ^1.1.10 copied to clipboard

A powerful and flexible HTTP client for Flutter applications, built on top of Dio with advanced features like request queueing, caching, and automatic token management.

🌊 Dio Flow #

pub package License: MIT Flutter

A powerful Flutter package that enhances Dio HTTP client with built-in support for caching, authentication, pagination, error handling, and standardized JSON utilities. Built for modern Flutter applications that need robust API integration.

📋 Table of Contents #

✨ Features #

  • 🚀 Modern HTTP Client: Built on top of Dio with enhanced features
  • 🔄 Smart Response Handling: Automatic conversion of responses to strongly-typed models
  • 💾 Intelligent Caching: Built-in response caching with configurable TTL
  • 🔑 Token Management: Robust authentication with token refresh support
  • 🔁 Auto-Retry: Configurable retry logic for failed requests
  • ⚡ Rate Limiting: Prevent API throttling with built-in rate limiting
  • 📶 Network Awareness: Automatic handling of connectivity changes
  • 📊 Request Metrics: Built-in performance tracking
  • 🔍 Detailed Logging: Complete request/response logging with cURL commands
  • 📄 Pagination Support: Built-in utilities for handling paginated responses
  • 🛡️ Type Safety: Strong typing throughout the library
  • 🎯 Error Handling: Comprehensive error handling with typed error responses

📦 Installation #

Add to your pubspec.yaml:

dependencies:
  dio_flow: ^1.1.7

🚀 Getting Started #

Basic Setup #

import 'package:dio_flow/dio_flow.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 1. Configure the client
  DioFlowConfig.initialize(
    baseUrl: 'https://api.example.com',
    connectTimeout: const Duration(seconds: 30),
    receiveTimeout: const Duration(seconds: 30),
    sendTimeout: const Duration(seconds: 30),
  );
  
  // 2. Initialize the client
  await ApiClient.initialize();
  
  runApp(MyApp());
}

🎯 Core Components #

DioRequestHandler #

The main class for making HTTP requests:

// GET request
final response = await DioRequestHandler.get(
  'users',
  parameters: {'role': 'admin'},
  requestOptions: RequestOptionsModel(
    hasBearerToken: true,
    shouldCache: true,
    retryOptions: RetryOptions(
      maxAttempts: 3,
      retryInterval: const Duration(seconds: 1),
    ),
  ),
);

// POST request with typed response
final loginResponse = await DioRequestHandler.post<LoginResponse>(
  'auth/login',
  data: {
    'email': 'user@example.com',
    'password': '********',
  },
  requestOptions: RequestOptionsModel(
    hasBearerToken: false,
  ),
);

Response Models #

All responses are wrapped in typed models:

if (response.isSuccess) {
  final data = response.data;
  // Handle success
} else {
  final error = response.error;
  switch (error.errorType) {
    case ErrorType.network:
      // Handle network error
      break;
    case ErrorType.validation:
      // Handle validation error
      break;
    case ErrorType.unauthorized:
      // Handle auth error
      break;
    // ... handle other error types
  }
}

Interceptors #

The package includes several built-in interceptors:

  1. MetricsInterceptor: Tracks request performance
  2. RateLimitInterceptor: Prevents API throttling
  3. DioInterceptor: Handles authentication and headers
  4. RetryInterceptor: Manages request retries
  5. ConnectivityInterceptor: Handles network state
  6. CacheInterceptor: Manages response caching

🔑 Authentication #

The package provides robust token management with persistent storage:

// Initialize token manager (call this in your main.dart)
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await TokenManager.initialize();
  runApp(MyApp());
}

// Setting tokens with persistence
await TokenManager.setTokens(
  accessToken: 'your_access_token',
  refreshToken: 'your_refresh_token',
  expiry: DateTime.now().add(Duration(hours: 1)),
);

// Getting access token (automatically handles refresh if needed)
final token = await TokenManager.getAccessToken();

// Clearing tokens
await TokenManager.clearTokens();

Key features:

  • Persistent token storage using SharedPreferences
  • Automatic token loading on app initialization
  • Token expiry tracking and automatic refresh
  • Secure token management with proper error handling
  • Asynchronous operations for better performance

Protected Requests #

// Make authenticated request
final response = await DioRequestHandler.get(
  'user/profile',
  requestOptions: RequestOptionsModel(
    hasBearerToken: true, // This will automatically include the token
  ),
);

// Handle token expiration
if (response.error?.errorType == ErrorType.unauthorized) {
  // Token expired, handle refresh or logout
}

🌐 Endpoint Configuration #

Basic Endpoints #

// Register endpoints
EndpointProvider.instance.register('login', '/auth/login');
EndpointProvider.instance.register('users', '/api/users');

// Use registered endpoints
final response = await DioRequestHandler.post(
  'login',
  data: {'email': email, 'password': password},
);

Dynamic Endpoints #

// Register endpoint with parameters
EndpointProvider.instance.register('user_details', '/api/users/{id}');

// Use with path parameters
final response = await DioRequestHandler.get(
  'user_details',
  pathParameters: {'id': '123'},
);

🔄 Advanced Features #

Caching #

// Enable caching for a request
final response = await DioRequestHandler.get(
  'users',
  requestOptions: RequestOptionsModel(
    shouldCache: true,
    cacheMaxAge: const Duration(minutes: 5),
  ),
);

// Clear cache
await ApiClient.clearCache();

Pagination #

// Using pagination utilities
final paginatedResponse = await DioRequestHandler.get(
  'posts',
  parameters: {
    'page': 1,
    'per_page': 20,
  },
);

final pagination = PaginationHelper.fromResponse(paginatedResponse);
final hasMore = pagination.hasNextPage;
final totalPages = pagination.totalPages;

JSON Utilities #

// Safe JSON parsing
final jsonData = JsonUtils.tryParseJson(rawJson);

// Access nested values safely
final nestedValue = JsonUtils.getNestedValue(
  jsonData,
  'user.profile.name',
  'Default Name',
);

Request Queueing #

// Queue multiple requests
final responses = await Future.wait([
  DioRequestHandler.get('users'),
  DioRequestHandler.get('posts'),
  DioRequestHandler.get('comments'),
]);

// Handle rate limiting automatically
final rateLimitedResponse = await DioRequestHandler.get(
  'high-frequency-endpoint',
  requestOptions: RequestOptionsModel(
    shouldRateLimit: true,
    rateLimit: 30, // requests per minute
  ),
);

Custom Response Types #

class PaginatedResponse<T> {
  final List<T> items;
  final int total;
  final int page;
  
  PaginatedResponse.fromJson(
    Map<String, dynamic> json,
    T Function(Map<String, dynamic>) converter,
  )   : items = (json['data'] as List)
            .map((item) => converter(item))
            .toList(),
        total = json['total'] ?? 0,
        page = json['page'] ?? 1;
}

// Use with typed response
final response = await DioRequestHandler.get<PaginatedResponse<User>>(
  'users',
  converter: (json) => PaginatedResponse.fromJson(
    json,
    (item) => User.fromJson(item),
  ),
);

🛠️ Best Practices #

  1. Initialize Early:

    void main() async {
      await ApiClient.initialize();
      // ... rest of your app initialization
    }
    
  2. Handle Errors Consistently:

    try {
      final response = await DioRequestHandler.get('endpoint');
      if (response.isSuccess) {
        // Handle success
      } else {
        // Use the typed error handling
        handleError(response.error);
      }
    } catch (e) {
      // Handle unexpected errors
    }
    
  3. Use Type-Safe Responses:

    class UserResponse {
      final String id;
      final String name;
         
      UserResponse.fromJson(Map<String, dynamic> json)
          : id = json['id'],
            name = json['name'];
    }
       
    final response = await DioRequestHandler.get<UserResponse>(
      'users/me',
      converter: (json) => UserResponse.fromJson(json),
    );
    

Error Handling Patterns #

// Create a reusable error handler
Future<T> handleApiResponse<T>(ResponseModel response) async {
  if (response.isSuccess) {
    return response.data as T;
  }

  switch (response.error?.errorType) {
    case ErrorType.network:
      throw NetworkException(response.error!.message);
    case ErrorType.unauthorized:
      await handleUnauthorized();
      throw AuthException(response.error!.message);
    case ErrorType.validation:
      throw ValidationException(response.error!.message);
    default:
      throw ApiException(response.error?.message ?? 'Unknown error');
  }
}

// Use in your code
try {
  final users = await handleApiResponse<List<User>>(
    await DioRequestHandler.get('users'),
  );
  // Use users data
} on NetworkException catch (e) {
  // Handle network error
} on AuthException catch (e) {
  // Handle auth error
} on ValidationException catch (e) {
  // Handle validation error
} on ApiException catch (e) {
  // Handle other API errors
}

Repository Pattern #

class UserRepository {
  Future<User> getCurrentUser() async {
    final response = await DioRequestHandler.get<User>(
      'users/me',
      requestOptions: RequestOptionsModel(
        hasBearerToken: true,
        shouldCache: true,
        cacheMaxAge: const Duration(minutes: 5),
      ),
      converter: (json) => User.fromJson(json),
    );
    
    return handleApiResponse<User>(response);
  }
  
  Future<void> updateProfile(UserUpdateRequest request) async {
    final response = await DioRequestHandler.put(
      'users/me',
      data: request.toJson(),
      requestOptions: RequestOptionsModel(hasBearerToken: true),
    );
    
    await handleApiResponse(response);
  }
}

🔍 Troubleshooting #

Common issues and solutions:

  1. Authentication Issues:

    • Ensure hasBearerToken is set correctly in RequestOptionsModel
    • Check if tokens are properly managed in TokenManager
  2. Caching Problems:

    • Verify shouldCache is enabled in request options
    • Check cache duration settings
    • Try clearing cache with ApiClient.clearCache()
  3. Network Errors:

    • Check connectivity status
    • Verify retry options are configured
    • Examine cURL logs for request details

Debug Mode #

// Enable detailed logging
DioFlowConfig.initialize(
  baseUrl: 'https://api.example.com',
  debugMode: true, // This will enable detailed logging
);

// Log specific requests
final response = await DioRequestHandler.get(
  'users',
  requestOptions: RequestOptionsModel(
    shouldLogRequest: true, // Log this specific request
  ),
);

🤝 Contributing #

Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.

📄 License #

This project is licensed under the MIT License - see the LICENSE file for details.

3
likes
140
points
60
downloads

Publisher

verified publishermoeinmoradi.ir

Weekly Downloads

A powerful and flexible HTTP client for Flutter applications, built on top of Dio with advanced features like request queueing, caching, and automatic token management.

Repository (GitHub)
View/report issues

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

colorful_log_plus, dio, flutter, log_curl_request, shared_preferences

More

Packages that depend on dio_flow