data_channel 5.0.0 copy "data_channel: ^5.0.0" to clipboard
data_channel: ^5.0.0 copied to clipboard

data_channel (DC) is a simple dart utility for handling exceptions and data routing.

example/main.dart

// ignore_for_file: avoid_print, unreachable_from_main

/// **Complete Implementation Examples:**
///
/// For comprehensive test examples and usage patterns, see:
/// - DC (Data Channel) tests: https://github.com/ganeshrvel/pub-data-channel/blob/master/test/data_channel_test.dart
/// - Option type tests: https://github.com/ganeshrvel/pub-data-channel/blob/master/test/option_test.dart

import 'dart:convert';
import 'dart:io';

import 'package:data_channel/data_channel.dart';
import 'package:data_channel/src/option.dart';

// Mock classes for examples
class User {
  User(this.id, this.name, {this.isVerified = false});

  final String id;
  final String name;
  final bool isVerified;

  @override
  String toString() => 'User(id: $id, name: $name, verified: $isVerified)';
}

class Profile {
  Profile(this.userId, this.bio);

  final String userId;
  final String bio;

  @override
  String toString() => 'Profile(userId: $userId, bio: $bio)';
}

class NetworkException implements Exception {
  NetworkException(this.message);

  final String message;

  @override
  String toString() => 'NetworkException: $message';
}

class ValidationException implements Exception {
  ValidationException(this.field);

  final String field;

  @override
  String toString() => 'ValidationException: $field';
}

class UserFacingException implements Exception {
  UserFacingException(this.message);

  final String message;

  @override
  String toString() => 'UserFacingException: $message';
}

void main() {
  dcAutoExample();
  dcFromOptionExample();
  basicDCUsageExample();
  optionBasicsExample();
  dcFoldExample();
  dcForwardErrorOrExample();
  dcForwardErrorOrElseExample();
  dcMapErrorExample();
  chainedOperationsExample();
  realWorldApiExample();
  nullHandlingWithOptionsExample();
  workflowExample();
  nonNullableGuaranteeExample();
}

void dcAutoExample() {
  // Simulating nullable data from database/API
  // ignore: unnecessary_nullable_for_final_variable_declarations
  const String? nullableUser = 'Alice';
  const String? nullValue = null;

  // DC.auto handles null check automatically
  final userResult = DC<NetworkException, String>.auto(nullableUser);
  final nullResult = DC<NetworkException, String>.auto(nullValue);

  print('With non-null value:');
  userResult.fold(
    onError: (e) => print('  Error: $e'),
    onData: (opt) => opt.fold(
      onSome: (user) => print('  User: $user'),
      onNone: () => print('  No user'),
    ),
  );
  // Expected:
  //   User: Alice

  print('With null value:');
  nullResult.fold(
    onError: (e) => print('  Error: $e'),
    onData: (opt) => opt.fold(
      onSome: (user) => print('  User: $user'),
      onNone: () => print('  No user'),
    ),
  );
  // Expected:
  //   No user

  // Practical example: database lookup
  final dbResult = simulateDatabaseLookup('123');
  print('Database lookup:');
  dbResult.fold(
    onError: (e) => print('  Error: ${e.message}'),
    onData: (opt) => opt.fold(
      onSome: (user) => print('  Found: ${user.name}'),
      onNone: () => print('  User not found'),
    ),
  );
  // Expected:
  //   Found: DatabaseUser
}

void dcFromOptionExample() {
  // Simulating Option returned from cache/computation
  final cachedProfile = getCachedProfile('user-123');

  // Lift Option directly into DC without double-wrapping
  final result = DC<NetworkException, Profile>.fromOption(cachedProfile);

  print('Cache lookup result:');
  result.fold(
    onError: (e) => print('  Error: ${e.message}'),
    onData: (opt) => opt.fold(
      onSome: (profile) => print('  Cached: ${profile.bio}'),
      onNone: () => print('  Cache miss'),
    ),
  );
  // Expected:
  //   Cached: Cached profile bio

  // Example: combining Option-returning functions with DC
  final processedResult = processOptionData();
  print('Processed Option:');
  processedResult.fold(
    onError: (e) => print('  Error: ${e.message}'),
    onData: (opt) => opt.fold(
      onSome: (data) => print('  Processed: $data'),
      onNone: () => print('  No data to process'),
    ),
  );
  // Expected:
  //   Processed: PROCESSED_VALUE
}

// Helper functions for examples
DC<NetworkException, User> simulateDatabaseLookup(String id) {
  // Simulate nullable database result
  // ignore: unnecessary_nullable_for_final_variable_declarations
  final User? dbUser = User(id, 'DatabaseUser');

  // Auto-handle nullable result
  return DC<NetworkException, User>.auto(dbUser);
}

Option<Profile> getCachedProfile(String userId) {
  // Simulate cache returning Option
  return Some(Profile(userId, 'Cached profile bio'));
}

DC<NetworkException, String> processOptionData() {
  // Simulate function that returns Option
  final optionData = Option.auto('processed_value');

  // Process Option and lift into DC
  final transformed = optionData.map((s) => s.toUpperCase());

  return DC<NetworkException, String>.fromOption(transformed);
}

void basicDCUsageExample() {
  // Success with data
  final successResult = DC<NetworkException, User>.some(
    User('123', 'Alice'),
  );

  print('Success case:');
  print('  hasError: ${successResult.hasError}');
  print('  hasOptionalData: ${successResult.hasOptionalData}');
  // Expected:
  //   hasError: false
  //   hasOptionalData: true

  // Error case
  final errorResult = DC<NetworkException, User>.error(
    NetworkException('Connection timeout'),
  );

  print('Error case:');
  print('  hasError: ${errorResult.hasError}');
  print('  hasOptionalData: ${errorResult.hasOptionalData}');
  // Expected:
  //   hasError: true
  //   hasOptionalData: false

  // Success with no data
  final nullDataResult = DC<NetworkException, User>.none();
  print('Null data case:');
  print('  hasError: ${nullDataResult.hasError}');
  print('  hasOptionalData: ${nullDataResult.hasOptionalData}');
  // Expected:
  //   hasError: false
  //   hasOptionalData: true
}

void optionBasicsExample() {
  // Creating Options
  const some = Some(42);
  const none = None<int>();
  final fromNullable = Option<int>.auto(null);

  print('Some(42).isSome: ${some.isSome}');
  // Expected: Some(42).isSome: true

  print('None().isNone: ${none.isNone}');
  // Expected: None().isNone: true

  print('Option<int>.auto(null).isNone: ${fromNullable.isNone}');
  // Expected: Option<int>.auto(null).isNone: true

  // Using Option methods
  final doubled = some.map((x) => x * 2);
  print('Some(42).map(x * 2): ${doubled.tryMaybe()}');
  // Expected: Some(42).map(x * 2): 84

  final withDefault = none.orElse(0);
  print('None().orElse(0): $withDefault');
  // Expected: None().orElse(0): 0

  // Filtering
  final evenOnly = some.filter((x) => x.isEven);
  print('Some(42).filter(isEven): ${evenOnly.tryMaybe()}');
  // Expected: Some(42).filter(isEven): 42
}

void dcFoldExample() {
  final successResult = DC<NetworkException, User>.some(
    User('789', 'Bob'),
  );

  final message = successResult.fold(
    onError: (error) => 'Failed: ${error.message}',
    onData: (userOption) => userOption.fold(
      onSome: (user) => 'Welcome ${user.name}!',
      onNone: () => 'No user data',
    ),
  );

  print('Success fold: $message');
  // Expected: Success fold: Welcome Bob!

  final errorResult = DC<NetworkException, User>.error(
    NetworkException('Network error'),
  );

  final errorMessage = errorResult.fold(
    onError: (error) => 'Error: ${error.message}',
    onData: (userOption) => 'User loaded',
  );

  print('Error fold: $errorMessage');
  // Expected: Error fold: Error: Network error
}

void dcForwardErrorOrExample() {
  final userResult = DC<NetworkException, User>.some(
    User('111', 'Charlie'),
  );

  final profileResult = DC.forwardErrorOr(
    userResult,
    Profile('111', 'Software Developer'),
  );

  print('User → Profile (success):');
  profileResult.fold(
    onError: (e) => print('  Error: $e'),
    onData: (profile) => print('  Profile: ${profile.tryMaybe()}'),
  );
  // Expected:
  //   Profile: Profile(userId: 111, bio: Software Developer)

  // Error case - forwards error
  final errorResult = DC<NetworkException, User>.error(
    NetworkException('Fetch failed'),
  );

  final errorProfileResult = DC.forwardErrorOr(
    errorResult,
    Profile('default', 'default'),
  );

  print('User → Profile (error):');
  errorProfileResult.fold(
    onError: (e) => print('  Error forwarded: $e'),
    onData: (profile) => print('  Profile: ${profile.tryMaybe()}'),
  );
  // Expected:
  //   Error forwarded: NetworkException: Fetch failed
}

void dcForwardErrorOrElseExample() {
  final userResult = DC<NetworkException, User>.some(
    User('222', 'Diana', isVerified: true),
  );

  // Example 1: Simple map
  final nameResult = DC.forwardErrorOrElse(
    userResult,
    (userData) => userData.map((user) => user.name),
  );

  print('Extract name:');
  nameResult.fold(
    onError: (e) => print('  Error: $e'),
    onData: (name) => print('  Name: ${name.orElse("Unknown")}'),
  );
  // Expected:
  //   Name: Diana

  // Example 2: Filter with validation
  final verifiedResult = DC.forwardErrorOrElse(
    userResult,
    (userData) => userData.filter((user) => user.isVerified),
  );

  print('Filter verified users:');
  verifiedResult.fold(
    onError: (e) => print('  Error: $e'),
    onData: (user) => user.fold(
      onSome: (u) => print('  Verified user: ${u.name}'),
      onNone: () => print('  User not verified'),
    ),
  );
  // Expected:
  //   Verified user: Diana

  // Example 3: Provide fallback
  final displayNameResult = DC.forwardErrorOrElse(
    userResult,
    (userData) => Some(userData.map((user) => user.name).orElse('Guest')),
  );

  print('With fallback:');
  displayNameResult.fold(
    onError: (e) => print('  Error: $e'),
    onData: (name) => print('  Display name: ${name.tryMaybe()}'),
  );
  // Expected:
  //   Display name: Diana
}

void dcMapErrorExample() {
  final result = DC<NetworkException, User>.error(
    NetworkException('502 Bad Gateway'),
  );

  // Transform to user-friendly error
  final friendlyResult = result.mapError(
    (error) => UserFacingException('Service temporarily unavailable'),
  );

  print('Original error:');
  result.fold<void>(
    onError: (e) => print('  $e'),
    onData: (_) => {},
  );
  // Expected:
  //   NetworkException: 502 Bad Gateway

  print('Mapped error:');
  friendlyResult.fold<void>(
    onError: (e) => print('  $e'),
    onData: (_) => {},
  );
  // Expected:
  //   UserFacingException: Service temporarily unavailable
}

void chainedOperationsExample() {
  final userResult = DC<NetworkException, User>.some(
    User('333', 'eve', isVerified: true),
  );

  DC
      .forwardErrorOrElse(
        userResult,
        (userData) => userData
            .filter((user) => user.isVerified)
            .map((user) => user.name)
            .map((name) => name.toUpperCase()),
      )
      .fold(
        onError: (e) => print('Error: $e'),
        onData: (name) => print('Processed name: ${name.orElse("UNKNOWN")}'),
      );
  // Expected: Processed name: EVE
}

void realWorldApiExample() {
  final apiResult = simulateApiCall('444');

  // Pattern 1: Using fold
  final greeting = apiResult.fold(
    onError: (error) => 'Could not load user: ${error.message}',
    onData: (userOption) => userOption.fold(
      onSome: (user) => 'Hello, ${user.name}!',
      onNone: () => 'No user found',
    ),
  );

  print('API response: $greeting');
  // Expected: API response: Hello, SimulatedUser!

  // Pattern 2: Transform and handle
  apiResult
      .mapError((error) => UserFacingException('Please try again later'))
      .fold(
        onError: (error) => print('UI Error: $error'),
        onData: (userOption) {
          userOption.fold(
            onSome: (user) => print('Display user: ${user.name}'),
            onNone: () => print('Show empty state'),
          );
        },
      );
  // Expected: Display user: SimulatedUser
}

void nullHandlingWithOptionsExample() {
  // Traditional nullable approach (avoid this)
  const String? nullableName = null;
  final traditionalResult = nullableName?.toUpperCase() ?? 'UNKNOWN';
  print('Traditional nullable: $traditionalResult');
  // Expected: Traditional nullable: UNKNOWN

  // Option approach (preferred)
  final nameOption = Option<String>.auto(null);
  final optionResult = nameOption.map((n) => n.toUpperCase()).orElse('UNKNOWN');
  print('Option approach: $optionResult');
  // Expected: Option approach: UNKNOWN

  // Chaining operations safely
  final processedName = Option.auto('alice')
      .filter((n) => n.length > 3)
      .map((n) => n.toUpperCase())
      .map((n) => 'User: $n')
      .orElse('Invalid name');

  print('Chained Option: $processedName');
  // Expected: Chained Option: User: ALICE
}

void workflowExample() {
  // Simulate: fetchUser → validateUser → createProfile
  final fetchResult = DC<NetworkException, User>.some(
    User('555', 'Frank', isVerified: true),
  );

  print('Step 1: Fetch user');
  fetchResult.fold(
    onError: (e) => print('   Failed: ${e.message}'),
    onData: (user) => print('   Fetched: ${user.tryMaybe()}'),
  );
  // Expected:
  //   Fetched: User(id: 555, name: Frank, verified: true)

  // Validate user is verified
  final validateResult = DC.forwardErrorOrElse(
    fetchResult,
    (userData) => userData.filter((user) => user.isVerified),
  );

  print('Step 2: Validate user');
  validateResult.fold(
    onError: (e) => print('   Failed: ${e.message}'),
    onData: (user) => user.fold(
      onSome: (u) => print('   Validated: ${u.name}'),
      onNone: () => print('   User not verified'),
    ),
  );
  // Expected:
  //   Validated: Frank

  // Create profile from validated user
  final profileResult = DC.forwardErrorOrElse(
    validateResult,
    (userData) =>
        userData.map((user) => Profile(user.id, 'Bio for ${user.name}')),
  );

  print('Step 3: Create profile');
  final finalMessage = profileResult.fold(
    onError: (e) => '   Failed: ${e.message}',
    onData: (profile) => profile.fold(
      onSome: (p) => '   Created: $p',
      onNone: () => '   Cannot create profile: user not verified',
    ),
  );

  print(finalMessage);
  // Expected:
  //   Created: Profile(userId: 555, bio: Bio for Frank)
}

void nonNullableGuaranteeExample() {
  // With extends Object, Some ALWAYS contains non-null values
  final userOption = Some(User('1', 'Alice'));

  if (userOption.isSome) {
    // Safe to unwrap - guaranteed non-null with extends Object
    final user = userOption.tryMaybe()!;
    print('User name length: ${user.name.length}'); // No null check needed!
    // Expected: User name length: 5
  }

  // DC.auto with dynamic values
  const dynamic apiResponse = 'test-data';
  final result1 = DC<NetworkException, String>.auto(apiResponse as String?);

  print('Dynamic non-null:');
  result1.fold(
    onError: (e) => print('  Error: $e'),
    onData: (opt) => opt.fold(
      onSome: (data) => print('  Data: $data'),
      onNone: () => print('  No data'),
    ),
  );
  // Expected: Data: test-data

  dynamic nullResponse;
  final result2 = DC<NetworkException, String>.auto(nullResponse as String?);

  print('Dynamic null:');
  result2.fold(
    onError: (e) => print('  Error: $e'),
    onData: (opt) => opt.fold(
      onSome: (data) => print('  Data: $data'),
      onNone: () => print('  No data'),
    ),
  );
  // Expected: No data

  print('\nKey point: isSome=true means value is GUARANTEED non-null!');
}

DC<NetworkException, User> simulateApiCall(String id) {
  // Simulate successful API call
  return DC<NetworkException, User>.some(
    User(id, 'SimulatedUser', isVerified: true),
  );
}

/* ================= Starwars HTTP Example ================= */

class StarwarsResponse {
  StarwarsResponse({
    required this.character,
    required this.age,
  });

  factory StarwarsResponse.fromJson(Map<String, dynamic> json) {
    return StarwarsResponse(
      character: json['character'] as String,
      age: json['age'] as int,
    );
  }

  final String character;
  final int age;

  Map<String, dynamic> toJson() {
    return {
      'character': character,
      'age': age,
    };
  }

  @override
  String toString() => 'StarwarsResponse(character: $character, age: $age)';
}

class StarwarsDataSource {
  Future<DC<Exception, StarwarsResponse>> getStarwarsCharacters() async {
    try {
      final client = HttpClient();
      final uri = Uri.parse('https://starwars-api.com/characters');

      final request = await client.getUrl(uri);
      final response = await request.close();
      final body = await response.transform(utf8.decoder).join();

      if (response.statusCode != 200) {
        return DC.error(Exception('HTTP ${response.statusCode}'));
      }

      if (body.isEmpty) {
        return DC.none(); // Success but no data
      }

      final data = StarwarsResponse.fromJson(
        json.decode(body) as Map<String, dynamic>,
      );

      return DC.some(data);
    } on Exception catch (e) {
      return DC.error(e);
    }
  }
}

class StarwarsController {
  final _dataSource = StarwarsDataSource();

  Future<DC<Exception, StarwarsResponse>> getStarwarsCharacters() async {
    final result = await _dataSource.getStarwarsCharacters();

    // Transform errors to user-friendly messages
    return result.mapError(
      (error) => Exception('Failed to load Star Wars data: $error'),
    );
  }
}

Future<void> starwarsHttpExample() async {
  final controller = StarwarsController();
  final result = await controller.getStarwarsCharacters();

  final message = result.fold(
    onError: (error) => 'Error: $error',
    onData: (data) => data.fold(
      onSome: (character) =>
          'Character: ${character.character}, Age: ${character.age}',
      onNone: () => 'No characters found',
    ),
  );

  print(message);
  // Expected (if API call succeeds):
  //   Character: Luke Skywalker, Age: 19
  // Expected (if API call fails):
  //   Error: Exception: Failed to load Star Wars data: ...
}
7
likes
160
points
149
downloads

Publisher

verified publisherganeshrvel.com

Weekly Downloads

data_channel (DC) is a simple dart utility for handling exceptions and data routing.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

meta

More

Packages that depend on data_channel