apix 1.1.0
apix: ^1.1.0 copied to clipboard
Production-ready Flutter/Dart API client with auth refresh queue, exponential retry, smart caching and error tracking (Sentry-ready). Powered by Dio.
[Apix Logo]
ApiX
Production-ready Flutter/Dart API client with auth refresh queue, exponential retry, smart caching and error tracking (Sentry-ready). Powered by Dio.
Why ApiX? #
Les développeurs Flutter passent un temps considérable à réimplémenter les mêmes patterns : refresh token, retry, cache, gestion d'erreurs. ApiX combine tout cela dans une solution clé-en-main.
| Problème | Solution ApiX |
|---|---|
| Race conditions refresh token | Refresh queue automatique |
| Retry avec backoff manuel | RetryInterceptor intégré |
| Cache complexe à configurer | Strategies prêtes à l'emploi |
| Erreurs mal typées | Hiérarchie d'exceptions granulaire |
Quick Start #
import 'package:apix/apix.dart';
// Simple - fonctionne immédiatement
final client = ApiClientFactory.create(baseUrl: 'https://api.example.com');
final response = await client.get<Map<String, dynamic>>('/users');
30 secondes du pub add à la première requête.
Installation #
dependencies:
apix: ^1.0.0
flutter pub get
Configuration Complète #
ApiX supporte une configuration déclarative avec 6 paramètres optionnels :
final tokenProvider = SecureTokenProvider();
final client = ApiClientFactory.create(
baseUrl: 'https://api.example.com',
// 🔐 Authentication avec refresh automatique
authConfig: AuthConfig(
tokenProvider: tokenProvider,
refreshEndpoint: '/auth/refresh',
onTokenRefreshed: (response) async {
final data = response.data as Map<String, dynamic>;
await tokenProvider.saveTokens(
data['access_token'] as String,
data['refresh_token'] as String,
);
},
),
// 🔄 Retry avec exponential backoff
retryConfig: const RetryConfig(
maxAttempts: 3,
retryStatusCodes: [500, 502, 503, 504],
),
// 💾 Cache intelligent
cacheConfig: CacheConfig(
strategy: CacheStrategy.networkFirst,
defaultTtl: const Duration(minutes: 5),
),
// 📊 Logging configurable
loggerConfig: const LoggerConfig(
level: LogLevel.info,
redactedHeaders: ['Authorization'],
),
// 🐛 Error tracking (Sentry, Crashlytics, etc.)
errorTrackingConfig: ErrorTrackingConfig(
onError: (e, {stackTrace, extra, tags}) async {
// Sentry
await Sentry.captureException(e, stackTrace: stackTrace);
// Firebase Crashlytics
FirebaseCrashlytics.instance.recordError(e, stackTrace);
// Custom / Debug
debugPrint('Error: $e');
},
),
// 📈 Request metrics (Firebase, Amplitude, etc.)
metricsConfig: const MetricsConfig(
onMetrics: (metrics) {
// Exemple avec votre service d'analytics
debugPrint('${metrics.method} ${metrics.path} - ${metrics.durationMs}ms');
},
),
);
Features #
🔐 Authentication & Secure Storage #
// SecureTokenProvider utilise flutter_secure_storage
final tokenProvider = SecureTokenProvider();
final client = ApiClientFactory.create(
baseUrl: 'https://api.example.com',
authConfig: AuthConfig(
tokenProvider: tokenProvider,
refreshEndpoint: '/auth/refresh',
onTokenRefreshed: (response) async {
final data = response.data as Map<String, dynamic>;
await tokenProvider.saveTokens(
data['access_token'] as String,
data['refresh_token'] as String,
);
},
),
);
// Après login
await tokenProvider.saveTokens(accessToken, refreshToken);
// Logout
await tokenProvider.clearTokens();
Refresh token queue : Si plusieurs requêtes échouent avec 401, une seule refresh est lancée et toutes les requêtes attendent puis réessaient automatiquement.
🔄 Retry avec Exponential Backoff #
final client = ApiClientFactory.create(
baseUrl: 'https://api.example.com',
retryConfig: const RetryConfig(
maxAttempts: 3,
retryStatusCodes: [500, 502, 503, 504],
baseDelayMs: 1000,
multiplier: 2.0, // 1s → 2s → 4s
),
);
// Désactiver retry pour une requête spécifique
final response = await client.get<Map<String, dynamic>>(
'/critical-endpoint',
options: Options(extra: {noRetryKey: true}),
);
💾 Cache Intelligent #
final client = ApiClientFactory.create(
baseUrl: 'https://api.example.com',
cacheConfig: CacheConfig(
strategy: CacheStrategy.networkFirst,
defaultTtl: const Duration(minutes: 5),
),
);
// Override par requête
final config = await client.get<Map<String, dynamic>>(
'/app-config',
options: Options(extra: {
'cacheStrategy': CacheStrategy.cacheFirst,
'cacheTtl': const Duration(hours: 24),
}),
);
// Forcer refresh
final fresh = await client.get<Map<String, dynamic>>(
'/users',
options: Options(extra: {'forceRefresh': true}),
);
| Strategy | Comportement |
|---|---|
cacheFirst |
Cache d'abord, network en background |
networkFirst |
Network d'abord, fallback cache |
cacheOnly |
Cache uniquement |
networkOnly |
Network uniquement |
📊 Logging #
final client = ApiClientFactory.create(
baseUrl: 'https://api.example.com',
loggerConfig: const LoggerConfig(
level: LogLevel.info,
redactedHeaders: ['Authorization', 'Cookie'],
),
);
| Level | Description |
|---|---|
none |
Aucun log |
error |
Erreurs uniquement |
warn |
Warnings + erreurs |
info |
Info + warnings + erreurs |
trace |
Tout (debug) |
🐛 Sentry Integration #
1. Initialisation Sentry (dans main.dart) :
void main() async {
await SentrySetup.init(
options: SentrySetupOptions.production(
dsn: 'https://xxx@xxx.ingest.sentry.io/xxx',
),
appRunner: () => runApp(const MyApp()),
);
}
// Ou mode développement (pas de traces/replays)
await SentrySetup.init(
options: SentrySetupOptions.development(
dsn: 'your-sentry-dsn',
),
appRunner: () => runApp(const MyApp()),
);
2. Configuration du client API :
final client = ApiClientFactory.create(
baseUrl: 'https://api.example.com',
errorTrackingConfig: ErrorTrackingConfig(
onError: (e, {stackTrace, extra, tags}) async {
await Sentry.captureException(
e,
stackTrace: stackTrace,
withScope: (scope) {
extra?.forEach((key, value) => scope.setExtra(key, value));
tags?.forEach((key, value) => scope.setTag(key, value));
},
);
},
onBreadcrumb: (data) {
Sentry.addBreadcrumb(Breadcrumb(
message: data['message'] as String?,
category: data['category'] as String?,
data: data['data'] as Map<String, dynamic>?,
));
},
),
);
| Option | Description |
|---|---|
captureStatusCodes |
Status HTTP à capturer (défaut: 5xx) |
captureRequestBody |
Inclure le body request (défaut: false) |
captureResponseBody |
Inclure le body response (défaut: true) |
redactedHeaders |
Headers à masquer (Authorization, Cookie...) |
Error Handling #
Hiérarchie d'exceptions #
ApiException
├── NetworkException
│ ├── TimeoutException
│ └── ConnectionException
└── HttpException
├── ClientException (4xx)
│ ├── UnauthorizedException (401)
│ ├── ForbiddenException (403)
│ └── NotFoundException (404)
└── ServerException (5xx)
Try-catch classique #
try {
final response = await client.get<Map<String, dynamic>>('/users');
} on NotFoundException catch (e) {
print('User not found: ${e.message}');
} on UnauthorizedException catch (e) {
print('Please login again');
} on NetworkException catch (e) {
print('Check your connection: ${e.message}');
} on ApiException catch (e) {
print('API error: ${e.message}');
}
Result pattern (fonctionnel) #
final result = await client.get<Map<String, dynamic>>('/users').getResult();
result.when(
success: (response) => print('Got ${response.data}'),
failure: (error) => print('Error: ${error.message}'),
);
// Ou avec pattern matching
if (result.isSuccess) {
final data = result.valueOrNull;
}
API Reference #
ApiClientFactory.create #
| Paramètre | Type | Description |
|---|---|---|
baseUrl |
String |
URL de base (required) |
connectTimeout |
Duration |
Timeout connexion (30s) |
receiveTimeout |
Duration |
Timeout réception (30s) |
headers |
Map<String, dynamic> |
Headers par défaut |
authConfig |
AuthConfig? |
Configuration auth |
retryConfig |
RetryConfig? |
Configuration retry |
cacheConfig |
CacheConfig? |
Configuration cache |
loggerConfig |
LoggerConfig? |
Configuration logging |
errorTrackingConfig |
ErrorTrackingConfig? |
Configuration error tracking |
metricsConfig |
MetricsConfig? |
Configuration metrics |
interceptors |
List<Interceptor>? |
Interceptors custom |
Interceptors intégrés #
| Interceptor | Ajouté via | Description |
|---|---|---|
AuthInterceptor |
authConfig |
Token injection + refresh queue |
RetryInterceptor |
retryConfig |
Retry avec backoff |
CacheInterceptor |
cacheConfig |
Cache multi-stratégies |
LoggerInterceptor |
loggerConfig |
Logging request/response |
ErrorTrackingInterceptor |
errorTrackingConfig |
Error tracking |
MetricsInterceptor |
metricsConfig |
Request metrics |
Example App #
A complete Flutter app demonstrating all ApiX features is available on GitHub:
[ApiX Example App]
Features demonstrated:
- 🔐 SecureTokenProvider with simplified refresh flow
- 💾 Cache strategies (CacheFirst, NetworkFirst, HttpCache)
- 🔄 Retry logic with exponential backoff
- 🐛 Sentry integration with error testing
- 📊 Request metrics and logging
Contributing #
Contributions are welcome! Please read our contributing guidelines first.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add 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.
Acknowledgments #
- Built on top of Dio
- Inspired by best practices from production Flutter apps
Made with ❤️ by Germinator