Skip to content

A powerful Flutter package that enhances Dio HTTP client with built-in support for caching, authentication, pagination, error handling, and standardized JSON utilities.

License

Notifications You must be signed in to change notification settings

Moein-dev/dio_flow

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

31 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🌊 Dio Flow

pub package License: MIT Flutter Platform

A powerful, production-ready Flutter package that supercharges Dio HTTP client

Built for modern Flutter applications that demand robust, scalable API integration

πŸ“– Documentation β€’ πŸš€ Quick Start β€’ πŸ’‘ Examples β€’ πŸ› Issues


🎯 Why Dio Flow?

Dio Flow transforms your API integration experience by providing enterprise-grade features out of the box:

  • πŸ”₯ Zero Configuration: Get started in minutes with sensible defaults
  • πŸ›‘οΈ Production Ready: Battle-tested with comprehensive error handling
  • πŸš€ Performance First: Built-in caching, retry logic, and request optimization
  • 🌐 Universal: Works seamlessly across all Flutter platforms
  • πŸ§ͺ Developer Friendly: Extensive mocking support for testing
  • πŸ“Š Observable: Built-in metrics and detailed logging

πŸ“‹ Table of Contents

✨ Features

πŸš€ Core πŸ”’ Security πŸ› οΈ Developer Experience
Modern HTTP Client Token Management Type-Safe Responses
Smart Response Handling Auto Token Refresh Comprehensive Testing
Intelligent Caching Request Authentication Built-in Mocking
Auto-Retry Logic Secure Token Storage Detailed Logging
🌐 Platform πŸ“Š Performance πŸ”§ Advanced
Universal Support Request Metrics GraphQL Support
Web Compatible Rate Limiting File Operations
Cross-Platform Network Awareness Pagination Utils
WASM Ready Connection Pooling JSON Utilities

🎯 Key Capabilities

  • πŸš€ Modern HTTP Client: Enhanced Dio with production-grade features
  • πŸ”„ Smart Response Handling: Automatic conversion to strongly-typed models
  • πŸ’Ύ Intelligent Caching: Configurable TTL with automatic invalidation
  • πŸ”‘ Token Management: Robust authentication with automatic refresh
  • πŸ” Auto-Retry: Configurable retry logic with exponential backoff
  • ⚑ Rate Limiting: Built-in throttling to prevent API abuse
  • πŸ“Ά Network Awareness: Automatic connectivity change handling
  • πŸ“Š Request Metrics: Built-in performance tracking and analytics
  • πŸ” Detailed Logging: Complete request/response logging with cURL commands
  • πŸ“„ Pagination Support: Utilities for handling paginated API responses
  • πŸ›‘οΈ Type Safety: Strong typing throughout the entire library
  • 🎯 Error Handling: Comprehensive error handling with typed responses
  • πŸ§ͺ Mock Support: Built-in mocking system for testing without real HTTP calls
  • πŸ”— GraphQL Support: Native GraphQL queries, mutations, and subscriptions
  • πŸ“ File Operations: Easy file upload/download with progress tracking
  • πŸ”§ Extensible Architecture: Plugin-based design for custom functionality

🎯 Platform Support

🌐 Universal Flutter Support - Write Once, Run Everywhere

Platform Support File Operations Network Checking WASM Compatible
πŸ“± iOS βœ… Full βœ… Complete βœ… Native βœ… Ready
πŸ€– Android βœ… Full βœ… Complete βœ… Native βœ… Ready
🌐 Web βœ… Full πŸ”„ Bytes Only* βœ… HTTP-based βœ… Compatible
πŸͺŸ Windows βœ… Full βœ… Complete βœ… Native βœ… Ready
🍎 macOS βœ… Full βœ… Complete βœ… Native βœ… Ready
🐧 Linux βœ… Full βœ… Complete βœ… Native βœ… Ready

πŸ’‘ Web Platform Note: File operations use Uint8List instead of File objects due to browser security restrictions. This ensures maximum compatibility while maintaining functionality.

πŸ“¦ Installation

Get started in seconds with a single dependency

πŸ“‹ Add Dependency

dependencies:
  dio_flow: ^1.3.3

πŸ”„ Install

flutter pub get

πŸ“± Import

import 'package:dio_flow/dio_flow.dart';

πŸ“¦ Development Dependencies (Optional)

For enhanced development experience:

dev_dependencies:
  dio_flow: ^1.3.3
  # For testing with mocks
  mockito: ^5.4.0
  # For integration testing
  integration_test:
    sdk: flutter

πŸš€ Getting Started

⚑ From zero to API calls in under 2 minutes

🎯 Quick Setup

import 'package:dio_flow/dio_flow.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // πŸ”§ Configure Dio Flow
  DioFlowConfig.initialize(
    baseUrl: 'https://api.example.com',
    connectTimeout: const Duration(seconds: 30),
    receiveTimeout: const Duration(seconds: 30),
    sendTimeout: const Duration(seconds: 30),
  );

  // πŸš€ Initialize the client
  await ApiClient.initialize();

  runApp(MyApp());
}

🎯 Your First API Call

// πŸ“‘ Make your first request
final response = await DioRequestHandler.get('users');

if (response is SuccessResponseModel) {
  print('βœ… Success: ${response.data}');
} else {
  print('❌ Error: ${response.error?.message}');
}

πŸ”§ Advanced Configuration

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // πŸ”§ Advanced configuration
  DioFlowConfig.initialize(
    baseUrl: 'https://api.example.com',
    connectTimeout: const Duration(seconds: 30),
    receiveTimeout: const Duration(seconds: 30),
    sendTimeout: const Duration(seconds: 30),

    // πŸ” Enable detailed logging
    debugMode: true,

    // πŸ”’ Security headers
    defaultHeaders: {
      'User-Agent': 'MyApp/1.0.0',
      'Accept': 'application/json',
    },

    // πŸ”„ Retry configuration
    retryOptions: RetryOptions(
      maxAttempts: 3,
      retryInterval: const Duration(seconds: 2),
    ),
  );

  // πŸ”‘ Initialize token management
  await TokenManager.initialize();

  // πŸš€ Initialize API 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 is SuccessResponseModel) {
  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

πŸ›‘οΈ Enterprise-grade authentication with automatic token management

Dio Flow provides a comprehensive authentication system with persistent storage, automatic token refresh, and seamless integration:

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

  // Register refresh logic (consumer-side). Optional β€” if you don't set it,
  // automatic refresh will be disabled and TokenManager will return null on expired token.
    TokenManager.setRefreshHandler((refreshToken) async {
    // Use DioRequestHandler to call refresh endpoint (consumer controls endpoint details).
    final refreshResp = await DioRequestHandler.post(
      'auth/refresh',
      data: {'refresh_token': refreshToken},
    );

    if (refreshResp is! SuccessResponseModel) {
      throw ApiException('Refresh failed: ${refreshResp.error?.message ?? 'unknown'}');
    }

    final data = refreshResp.data!;
    final expiresIn = (data['expires_in'] as int?) ?? 3600;

    return RefreshTokenResponse(
      accessToken: data['access_token'] as String,
      refreshToken: data['refresh_token'] as String,
      expiry: DateTime.now().add(Duration(seconds: expiresIn)),
    );
  });

  runApp(MyApp());
}

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

// Check presence/validity (auto-refresh performed if expired and handler is set)
final hasToken = await TokenManager.hasAccessToken(); // returns true/false

// Getting access token (will automatically refresh if expired and a handler is configured)
final token = await TokenManager.getAccessToken(); // can be null if no handler and expired

// Clearing tokens
await TokenManager.clearTokens();

Key features:

  • Persistent token storage using SharedPreferences (tokens survive app restarts).

  • RefreshTokenHandler typedef β€” consumer provides a small callback that receives the refresh token and returns a RefreshTokenResponse (access, refresh, expiry). This keeps the package network-agnostic.

  • TokenManager.setRefreshHandler(...) β€” register refresh logic at startup or runtime (optional).

  • Automatic refresh behavior β€” hasAccessToken() and getAccessToken() will automatically attempt to refresh when the access token is expired only if a refresh handler has been set. If no handler is provided, expired tokens are treated as absent (methods return false/null).

-Single-flight refresh protection β€” internal _refreshCompleter ensures only one refresh request runs at a time; concurrent callers wait for that single result.

  • Tokens returned by the refresh handler are persisted via setTokens() (keeps state consistent across restarts).

  • All token operations are asynchronous for non-blocking startup and safer IO.

  • getAccessToken() behavior is unified: it either returns a valid token, triggers refresh (if handler exists), or returns null (if expired and no handler).

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

πŸ’‘ Production-tested patterns for robust API integration

πŸš€ Essential Patterns

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 is SuccessResponseModel) {
    // 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 is SuccessResponseModel) {
    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);
  }
}

⚑ Performance Tips

πŸš€ Optimization strategies for production apps

// πŸ’Ύ Use caching strategically
final response = await DioRequestHandler.get(
  'static-data',
  requestOptions: RequestOptionsModel(
    shouldCache: true,
    cacheMaxAge: const Duration(hours: 1), // Cache static data longer
  ),
);

// πŸ”„ Batch requests when possible
final futures = [
  DioRequestHandler.get('users'),
  DioRequestHandler.get('posts'),
  DioRequestHandler.get('comments'),
];
final responses = await Future.wait(futures);

// ⚑ Use pagination for large datasets
final paginatedResponse = await PaginationUtils.fetchAllPages(
  'large-dataset',
  parameters: {'per_page': 50}, // Optimal page size
  maxPages: 10, // Limit to prevent memory issues
);

// 🎯 Optimize file uploads
final uploadResponse = await FileHandler.uploadBytes(
  'upload',
  compressedBytes, // Compress before upload
  'file.jpg',
  onProgress: (sent, total) {
    // Update UI efficiently
    if (sent % 1024 == 0) { // Update every KB
      updateProgressUI(sent / total);
    }
  },
);

πŸ§ͺ Mock Support

🎭 Powerful mocking system for comprehensive testing

Dio Flow includes a sophisticated mocking system that makes testing your API integrations effortless:

void main() {
  // Enable mock mode
  MockDioFlow.enableMockMode();

  // Register mock responses
  MockDioFlow.mockResponse(
    'users',
    MockResponse.success([
      {'id': 1, 'name': 'John Doe'},
      {'id': 2, 'name': 'Jane Smith'},
    ]),
  );

  // Register response queue for testing pagination
  MockDioFlow.mockResponseQueue('posts', [
    MockResponse.success({'page': 1, 'data': [...]}),
    MockResponse.success({'page': 2, 'data': [...]}),
  ]);

  // Your tests will now use mocked responses
  final response = await DioRequestHandler.get('users');

  // Disable mock mode
  MockDioFlow.disableMockMode();
}

πŸ”— GraphQL Support

⚑ Native GraphQL integration with powerful query building

Dio Flow provides first-class GraphQL support with an intuitive query builder and comprehensive operation handling:

// Simple query
final response = await GraphQLHandler.query('''
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
''', variables: {'id': '123'});

// Using query builder
final query = GraphQLQueryBuilder.query()
    .operationName('GetUsers')
    .variables({'first': 'Int'})
    .body('users(first: $first) { id name }')
    .build();

// Mutations
final mutationResponse = await GraphQLHandler.mutation('''
  mutation CreateUser($input: CreateUserInput!) {
    createUser(input: $input) {
      id
      name
    }
  }
''', variables: {'input': {'name': 'John', 'email': 'john@example.com'}});

// Batch operations
final operations = [
  GraphQLOperation(query: 'query { users { id } }'),
  GraphQLOperation(query: 'query { posts { id } }'),
];
final batchResponse = await GraphQLHandler.batch(operations);

πŸ“ File Operations

πŸ“€πŸ“₯ Seamless file handling across all platforms with progress tracking

Dio Flow provides comprehensive file operations that work consistently across all Flutter platforms, with intelligent platform-specific optimizations:

Mobile/Desktop Platforms (iOS, Android, Windows, macOS, Linux)

// File upload from File object
final file = File('/path/to/file.jpg');
final uploadResponse = await FileHandler.uploadFile(
  'upload',
  file,
  fieldName: 'avatar',
  additionalData: {'userId': '123'},
  onProgress: (sent, total) {
    print('Upload: ${(sent/total*100).toStringAsFixed(1)}%');
  },
);

// File download to disk
final downloadResponse = await FileHandler.downloadFile(
  'files/123/download',
  '/local/path/file.pdf',
  onProgress: (received, total) {
    print('Download: ${(received/total*100).toStringAsFixed(1)}%');
  },
);

Web Platform

// Upload from bytes (web-compatible)
final bytesUploadResponse = await FileHandler.uploadBytes(
  'upload',
  fileBytes, // Uint8List
  'filename.jpg',
  fieldName: 'avatar',
  additionalData: {'userId': '123'},
);

// Download as bytes (web-compatible)
final bytesResponse = await FileHandler.downloadBytes('files/123');
if (bytesResponse is SuccessResponseModel) {
  final bytes = bytesResponse.data['bytes'] as Uint8List;
  // Use bytes for web download (e.g., trigger browser download)
}

Cross-Platform File Operations

// Multiple file upload (use bytes for web compatibility)
final files = {
  'document': documentBytes, // Uint8List for web
  'image': imageBytes,       // Uint8List for web
};
final multiUploadResponse = await FileHandler.uploadMultipleFiles(
  'upload-multiple',
  files,
);

// Upload from bytes (works on all platforms)
final bytesUploadResponse = await FileHandler.uploadBytes(
  'upload',
  fileBytes,
  'filename.jpg',
);

Note: On web platform, direct file system access is restricted by browser security. Use FileHandler.uploadBytes() and FileHandler.downloadBytes() for web-compatible file operations.

πŸ” Troubleshooting

πŸ› οΈ Quick solutions to common issues

🚨 Common Issues & 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
  4. Mock Issues:

    • Ensure MockDioFlow.enableMockMode() is called before requests
    • Verify mock responses are registered for the correct endpoints
    • Check that endpoint paths match exactly
  5. GraphQL Errors:

    • Validate GraphQL syntax in queries
    • Check that variables match the schema
    • Ensure the GraphQL endpoint is correctly configured
  6. File Upload Issues:

    • Verify file permissions and paths
    • Check server file size limits
    • Ensure correct Content-Type headers for multipart uploads

πŸ” 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
  ),
);

πŸ“š Quick Reference

⚑ Common operations at a glance

πŸ”— API Calls Cheat Sheet

// GET request
final users = await DioRequestHandler.get('users');

// POST with data
final created = await DioRequestHandler.post('users', data: userData);

// PUT update
final updated = await DioRequestHandler.put('users/123', data: updates);

// DELETE
final deleted = await DioRequestHandler.delete('users/123');

// With authentication
final profile = await DioRequestHandler.get(
  'profile',
  requestOptions: RequestOptionsModel(hasBearerToken: true),
);

// With caching
final cached = await DioRequestHandler.get(
  'static-data',
  requestOptions: RequestOptionsModel(
    shouldCache: true,
    cacheMaxAge: Duration(minutes: 30),
  ),
);

πŸ”‘ Authentication Cheat Sheet

// Set tokens
await TokenManager.setTokens(
  accessToken: 'access_token',
  refreshToken: 'refresh_token',
  expiry: DateTime.now().add(Duration(hours: 1)),
);

// Check if authenticated
final isAuthenticated = await TokenManager.hasAccessToken();

// Get current token (auto-refreshes if needed)
final token = await TokenManager.getAccessToken();

// Clear tokens (logout)
await TokenManager.clearTokens();

πŸ“ File Operations Cheat Sheet

// Upload file (mobile/desktop)
final upload = await FileHandler.uploadFile('upload', file);

// Upload bytes (all platforms)
final upload = await FileHandler.uploadBytes('upload', bytes, 'file.jpg');

// Download file (mobile/desktop)
final download = await FileHandler.downloadFile('files/123', '/path/file.pdf');

// Download bytes (all platforms)
final download = await FileHandler.downloadBytes('files/123');

πŸ§ͺ Testing Cheat Sheet

// Enable mocking
MockDioFlow.enableMockMode();

// Mock success response
MockDioFlow.mockResponse('users', MockResponse.success([...]));

// Mock error response
MockDioFlow.mockResponse('users', MockResponse.failure('Error message'));

// Mock network error
MockDioFlow.mockResponse('users', MockResponse.networkError());

// Disable mocking
MockDioFlow.disableMockMode();

🀝 Contributing

We love contributions! Help make Dio Flow even better πŸ’ͺ

Contributions are welcome! Whether it's:

  • πŸ› Bug Reports: Found an issue? Let us know!
  • ✨ Feature Requests: Have an idea? We'd love to hear it!
  • πŸ“– Documentation: Help improve our docs
  • πŸ”§ Code Contributions: Submit a PR!

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

🌟 Contributors

Thanks to all our amazing contributors who help make Dio Flow better!


πŸ“„ License

πŸ“œ MIT License - Free for everyone, everywhere

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

Made with ❀️ by the Flutter community


🌟 Star us on GitHub if Dio Flow helped you

GitHub stars

Happy coding! πŸš€

About

A powerful Flutter package that enhances Dio HTTP client with built-in support for caching, authentication, pagination, error handling, and standardized JSON utilities.

Resources

License

Stars

Watchers

Forks

Packages

No packages published