timsoftdz_network 2.0.0 copy "timsoftdz_network: ^2.0.0" to clipboard
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');
}
4
likes
150
points
8
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

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.

Topics

#networking #http #rest #retry #interceptor

License

MIT (license)

Dependencies

http

More

Packages that depend on timsoftdz_network