timsoftdz_network 2.0.0
timsoftdz_network: ^2.0.0 copied to clipboard
Enterprise-grade HTTP networking framework for Dart & Flutter. Interceptor-based, retry-safe, circuit-breaker support, Result<T> API, and zero-crash serialization. Inspired by Dio and Retrofit.
example/main.dart
// example/main.dart
// ============================================================
// timsoftdz_network v2 — Enterprise networking showcase.
//
// Demonstrates:
// 1. Basic GET with typed parser
// 2. Debug mode with timeline
// 3. Retry with exponential backoff
// 4. Circuit breaker
// 5. Auth interceptor with token refresh
// 6. Safe serialization (no crashes)
// 7. CancelToken + CancelGroup
// 8. GlobalErrorBoundary observer
// 9. Result<T> fluent API
// ============================================================
import 'package:timsoftdz_network/timsoftdz_network.dart';
// ── Model ─────────────────────────────────────────────────────────────────────
class Post {
final int id;
final String title;
final String body;
const Post({required this.id, required this.title, required this.body});
@override
String toString() => 'Post{id=$id, title="$title"}';
}
class PostParser extends Parser<Post> {
const PostParser();
@override
Post parse(Object? data) {
final map = data as Map<String, dynamic>;
return Post(
id : map['id'] as int,
title: map['title'] as String,
body : map['body'] as String,
);
}
}
// ── 1. Basic setup ────────────────────────────────────────────────────────────
Future<void> basicSetup() async {
print('\n=== 1. Basic GET with typed parser ===');
final api = TimNetwork(
baseUrl: 'https://jsonplaceholder.typicode.com',
options: NetworkOptions(
timeout : Duration(seconds: 15),
maxRetryAttempts : 3,
),
);
final result = await api.get<Post>(
'/posts/1',
parser: const PostParser(),
);
result.fold(
onSuccess: (r) {
print('✓ Status : ${r.statusCode}');
print(' Data : ${r.data}');
print(' Elapsed : ${r.elapsed.inMilliseconds}ms');
print(' RequestId : ${r.requestId}');
print(' RespSize : ${r.responseSizeBytes}B');
},
onFailure: (e) => print('✗ Error: ${e.message}'),
);
api.close();
}
// ── 2. Debug mode with timeline ───────────────────────────────────────────────
Future<void> debugModeExample() async {
print('\n=== 2. Debug mode with timeline ===');
// Enable debug globally.
TimNetwork.debugMode = true;
final api = TimNetwork(
baseUrl : 'https://jsonplaceholder.typicode.com',
interceptors: [
LoggingInterceptor(
detail : LogDetail.headers,
showTimeline: true, // Render ASCII timeline on each response.
maxBodyLength: 200,
),
],
);
final result = await api.get<dynamic>('/posts/1');
result.onSuccess((r) => print(' Got status: ${r.statusCode}'));
TimNetwork.debugMode = false;
api.close();
}
// ── 3. Retry with exponential backoff ─────────────────────────────────────────
Future<void> retryExample() async {
print('\n=== 3. Retry with exponential backoff ===');
final adapter = DefaultHttpAdapter();
final api = TimNetwork(
baseUrl : 'https://jsonplaceholder.typicode.com',
adapter : adapter,
interceptors: [
RetryInterceptor(
policy: ExponentialBackoffPolicy(
maxAttempts : 3,
baseDelay : Duration(milliseconds: 100),
maxDelay : Duration(seconds: 5),
retryOn : {408, 429, 500, 502, 503, 504},
retryOnError: true,
onRetry : ({
required attempt,
required statusCode,
required error,
required delay,
}) {
print(' ↺ Retry #$attempt after ${delay.inMilliseconds}ms '
'(status=$statusCode, error=$error)');
},
),
adapter: adapter,
),
LoggingInterceptor(detail: LogDetail.minimal),
],
options: NetworkOptions(maxRetryAttempts: 3),
);
final result = await api.get<dynamic>('/posts/1');
result.fold(
onSuccess: (r) => print(' ✓ Got ${r.statusCode} after ${r.retryCount} retries'),
onFailure: (e) => print(' ✗ Error: ${e.message}'),
);
api.close();
}
// ── 4. Circuit breaker ────────────────────────────────────────────────────────
Future<void> circuitBreakerExample() async {
print('\n=== 4. Circuit breaker ===');
final breaker = CircuitBreaker(
name : 'ExampleService',
failureThreshold: 3,
resetTimeout : Duration(seconds: 5),
onStateChange : (event) => print(' ⚡ Circuit: $event'),
);
// Test the circuit directly.
for (var i = 0; i < 4; i++) {
try {
await breaker.call(() async {
if (i < 3) throw Exception('Simulated failure');
return 'success';
});
print(' ✓ Call $i succeeded');
} on CircuitOpenException {
print(' ✗ Call $i BLOCKED — circuit is OPEN');
} catch (e) {
print(' ✗ Call $i failed: $e');
}
}
print(' Circuit state: ${breaker.state}');
breaker.dispose();
}
// ── 5. Auth interceptor ───────────────────────────────────────────────────────
Future<void> authExample() async {
print('\n=== 5. Auth interceptor with token storage ===');
// In-memory token storage for demo.
final tokenStorage = MemoryTokenStorage();
await tokenStorage.saveTokens(access: 'my-access-token-12345');
// No-op refresher for this demo (real app would implement TokenRefresher).
final refresher = NoOpTokenRefresher();
final adapter = DefaultHttpAdapter();
final api = TimNetwork(
baseUrl : 'https://jsonplaceholder.typicode.com',
adapter : adapter,
interceptors: [
HeadersInterceptor.static({
'X-App-Version' : '2.0.0',
'X-Platform' : PlatformInfo.platform.name,
}),
AuthInterceptor(
storage : tokenStorage,
refresher: refresher,
adapter : adapter,
),
LoggingInterceptor(
detail : LogDetail.headers,
maxBodyLength: 100,
),
],
);
final result = await api.get<dynamic>('/users/1');
result.fold(
onSuccess: (r) => print(' ✓ Authenticated request succeeded: ${r.statusCode}'),
onFailure: (e) => print(' ✗ Error: ${e.message}'),
);
api.close();
}
// ── 6. Safe serialization ─────────────────────────────────────────────────────
Future<void> safeSerializationExample() async {
print('\n=== 6. Safe serialization (no crashes) ===');
// Parser.tryParse — returns Result instead of throwing.
final goodData = {'id': 1, 'title': 'Hello', 'body': 'World'};
final badData = 'this_is_not_a_post';
final r1 = const PostParser().tryParse(goodData);
final r2 = const PostParser().tryParse(badData);
r1.fold(
onSuccess: (p) => print(' ✓ Parsed: $p'),
onFailure: (e) => print(' ✗ Error: ${e.message}'),
);
r2.fold(
onSuccess: (p) => print(' ✓ Parsed: $p (unexpected)'),
onFailure: (e) => print(' ✓ Safely caught: ${e.runtimeType}: ${e.message}'),
);
// SafeParser with fallback.
final fallbackParser = SafeParser(
const PostParser(),
fallback: Post(id: 0, title: 'Unknown', body: ''),
);
final fallbackResult = fallbackParser.parse('bad data');
print(' ✓ SafeParser fallback: $fallbackResult');
// Result.guard.
final guardResult = Result.guard(() {
throw Exception('Simulated parse failure');
});
guardResult.fold(
onSuccess: (_) => print(' Unexpected success'),
onFailure: (e) => print(' ✓ Result.guard caught: ${e.code}'),
);
}
// ── 7. CancelToken + CancelGroup ─────────────────────────────────────────────
Future<void> cancelTokenExample() async {
print('\n=== 7. CancelToken + CancelGroup ===');
// Create a group for related requests.
final group = CancelGroup();
final t1 = group.createToken();
final t2 = group.createToken();
print(' Active tokens before cancel: ${group.activeCount}');
group.cancelAll('Navigation away');
print(' t1 cancelled: ${t1.isCancelled} (${t1.reason})');
print(' t2 cancelled: ${t2.isCancelled}');
print(' Active tokens after cancel : ${group.activeCount}');
// Auto-cancellation demo.
final autoToken = CancelToken.withTimeout(Duration(milliseconds: 100));
print(' AutoToken before: isCancelled=${autoToken.isCancelled}');
await Future<void>.delayed(Duration(milliseconds: 150));
print(' AutoToken after 150ms: isCancelled=${autoToken.isCancelled}');
print(' Reason: ${autoToken.cancellationReason}');
}
// ── 8. GlobalErrorBoundary observer ──────────────────────────────────────────
Future<void> errorObserverExample() async {
print('\n=== 8. GlobalErrorBoundary observer (APM integration) ===');
// Register a mock APM observer.
GlobalErrorBoundary.addObserver((error, st, requestId) {
print(' 📡 APM received error for [$requestId]: ${error.runtimeType}');
});
final api = TimNetwork(
baseUrl: 'https://nonexistent.invalid.domain.test',
);
// This will fail with ConnectionException, triggering the observer.
final result = await api.get<dynamic>('/test');
result.fold(
onSuccess: (_) => print(' Unexpected success'),
onFailure: (e) => print(' ✓ Error returned to caller: ${e.runtimeType}'),
);
GlobalErrorBoundary.clearObservers();
api.close();
}
// ── 9. Result<T> fluent API ───────────────────────────────────────────────────
void resultFluentApiExample() {
print('\n=== 9. Result<T> fluent API ===');
final success = Success(42);
final failure = Failure<int>(TimNetworkException('test', code: 'X'));
// Method chaining.
success
.onSuccess((v) => print(' ✓ onSuccess: $v'))
.map((v) => v * 2)
.onSuccess((v) => print(' ✓ After map: $v'))
.mapSafe<String>((v) => 'value=$v')
.onSuccess((v) => print(' ✓ After mapSafe: $v'));
failure
.onFailure((e) => print(' ✓ onFailure: ${e.message}'))
.recover((_) => Success(99))
.onSuccess((v) => print(' ✓ Recovered: $v'));
// getOrDefault / getOrElse.
print(' ✓ getOrDefault: ${failure.getOrDefault(-1)}');
print(' ✓ getOrElse: ${failure.getOrElse(() => -99)}');
// fold.
final label = success.fold(
onSuccess: (v) => 'Got $v',
onFailure: (e) => 'Error: ${e.code}',
);
print(' ✓ fold: $label');
}
// ── Main ──────────────────────────────────────────────────────────────────────
Future<void> main() async {
print('┌────────────────────────────────────────────────┐');
print('│ timsoftdz_network v2 — Enterprise Showcase │');
print('└────────────────────────────────────────────────┘');
// Register safe logger output.
timNetworkLogger.minLevel = LogLevel.warning;
// Run all examples.
await basicSetup();
await debugModeExample();
await retryExample();
await circuitBreakerExample();
await authExample();
await safeSerializationExample();
await cancelTokenExample();
await errorObserverExample();
resultFluentApiExample();
print('\n✅ All examples completed.\n');
}