doso 1.1.1 copy "doso: ^1.1.1" to clipboard
doso: ^1.1.1 copied to clipboard

DoSo is a lightweight Dart library for simple and elegant error and state handling, designed to reduce boilerplate and avoid code generation in Flutter apps.

doso logo

Static Badge Static Badge Static Badge

DoSo: Simple and Elegant Error and State Handling #

DoSo is a lightweight Dart library designed to simplify and streamline state and error handling in Flutter applications. Built to reduce boilerplate and avoid code generation, DoSo provides a functional and expressive API for managing synchronous and asynchronous operations.


🚀 Getting Started #

DoSo offers a declarative and functional approach to handling common states such as:

  • initial
  • loading
  • success
  • failure

This encapsulates logic and enhances error handling throughout your app. Check out the example below to see how DoSo can be used in your Flutter projects.

import 'package:doso/doso.dart';

void main() async {
  // [Do] represents a action. (State with a value of type S and an optional failure of type F)
  // [So] represents a return. (Is a type alias for Do<F, S> and SoException<Exception, S> for a fixed failure type)
  
  // EMITTING STATES
  // Use Do.tryCatch to handle sync or async operations
  final result = await Do.tryCatch(
    onTry: () => doSomething(),
    onCatch: (exception, stackTrace) => Exception('Captured error: $exception and $stackTrace'), // optional
    onFinally: () => print('finished'), // optional
  );
  // or
  // Use Do states directly with a common try/catch
  try {
    final result = doSomething();
    return Do.success(result);
  } catch (e) {
    return Do.failure(e);
  } finally {
    print('finished');
  }

  // STATE HANDLING
  // Handle all Do states with when:
  result.when(
    onInitial: () => print('Initial State'), // optional
    onLoading: () => print('Loading...'),
    onSuccess: (value) => print('Success: $value'),
    onFailure: (failure) => print('Failure: $failure'),
  );
  // or
  // Handle only Do.success and Do.failure with fold:
  result.fold(
    onFailure: (failure) => print('Failure: $failure'),
    onSuccess: (value) => print('Success: $value'),
  );
  // or
  // Handle specific states with maybeWhen:
  final output = result.maybeWhen(
    onInitial: () => 'Initial', // optional
    onLoading: () => 'Loading', // optional
    onSuccess: (value) => 'Success: $value', // optional
    onFailure: (failure) => 'Failure: $failure', // optional
    orElse: () => 'Else' // optional
  );
  // or
  // Just use a simple if statement:
  if (result.isInitial) {}
  if (result.isLoading) {}
  if (result.isSuccess) {} 
  if (result.isFailure) {}
  
  // RETURN STATEMENTS
  // So with custom failure type
  final So<MyCustomFailure, int> result = Do.success(42);
  
  // So with failure fixed exception type
  final SoException<String> result2 = Do.success('String');
}

📦 Available States #

// Do
Do.initial();            // Represents the initial state
Do.loading();            // Represents a loading state
Do.success(value);       // Represents a success with the associated value [S]
Do.failure([failure]);   // Represents a failure with optional failure [F]

// So
So<F, S>                 // Represents a return statement with a failure of type F and a value of type S
SoException<S>           // Represents a return statement with a fixed failure type of Exception and a value of type S

🔑 Core Methods #

getOrElse(S defaultValue) #

Returns the value in Do.success, or the fallback value if it is not available.

final value = Do.success(42).getOrElse(0); // 42

map<T>(T Function(S value)) #

Transforms the success value into another value.

final result = Do.success(42).map((value) => value.toString()); // Do<String>.success('42')

flatMap<T>(Do<T, F> Function(S value)) #

Maps to another Do state.

final result = Do.success(42).flatMap((value) => Do.success(value.toString()));

fold<T> #

Handle success and failure.

final message = Do.success(42).fold(
  onFailure: (failure) => 'Error: $failure',
  onSuccess: (value) => 'Success: $value',
);

when<T> #

Handle all states.

final output = result.when(
  onInitial: () => 'Initial',
  onLoading: () => 'Loading...',
  onSuccess: (value) => 'Success: $value',
  onFailure: (failure) => 'Failure: $failure',
);

maybeWhen<T> #

Handle specific states with orElse.

final result = Do.success(42);
final output = result.maybeWhen(
  onSuccess: (value) => 'Success: $value', // optional
  orElse: () => 'Default', // optional
);

print(output); // Output: Success: 42

tryCatch #

Wraps synchronous/async calls in a Do, automatically catching exceptions.

final result = await Do.tryCatch(
  onTry: () async => 42,
  onCatch: (exception, stackTrace) => Exception('Handled: $exception and $stackTrace'),
  onFinally: () => print('Done'),
);

📚 Use in Application Layers #

✅ Data Source #

SoException<int> getOk() => Do.tryCatch(onTry: () => http.get('/ok'));

✅ Repository #

SoException<String> getOk() async {
  final result = await dataSource.getOk();
  return result.map((code) => code.toString());
}

or

So<CustomFailure, String> getOk() async {
  final result = await dataSource.getOk();
  return result.flatMap((code) {
    if (code == is2XX() || code == is3XX()) {
      return Do.success(code.toString());
    } else {
      return Do.failure(CustomFailure('Error: $code'));
    }
  });
}

✅ Cubit #

class MyCubit extends Cubit<Do<Exception, String>> {
  MyCubit(this.repo) : super(Do.initial());
  
  final Repository repo;

  Future<void> getData() async {
    emit(Do.loading());
    
    final result = await repo.getOk();
    result.fold(
      onFailure: (failure) => emit(Do.failure(failure)),
      onSuccess: (value) => emit(Do.success('Success: $value')),
    );
  }
}

✅ UI #

BlocBuilder<MyCubit, Do<Exception, String>>(
  builder: (context, state) => state.when(
    onInitial: () => Text('Initial'),
    onLoading: () => CircularProgressIndicator(),
    onSuccess: (value) => Text(value),
    onFailure: (failure) => Text(failure.toString()),
  ),
)

Explore a practical example: DoSo Example


⚠️ When to Use DoSo #

DoSo is ideal for simple screen states or flows with a clear success/failure logic. You can combine it with other libraries like flutter_bloc or provider for state management.

If your state grows in complexity (e.g., multiple fields, nested properties), you might want to adopt a more traditional and robust approach like using manually crafted classes or more advanced tools.


DoSo is not: #

  • A replacement for functional programming libraries like dartz or fpdart. It is a simple and elegant solution for handling states and errors in a functional way, without the need follow entire functional approach.
  • A replacement for state management libraries like flutter_bloc or provider. It is a lightweight library that can be used in conjunction with these libraries to simplify state and error handling.

🔗 More Info & Contribute #

Check out the full implementation, open issues, and contribute on GitHub: DoSo on GitHub


11
likes
160
points
620
downloads

Publisher

verified publishergruposbf.com.br

Weekly Downloads

DoSo is a lightweight Dart library for simple and elegant error and state handling, designed to reduce boilerplate and avoid code generation in Flutter apps.

Repository (GitHub)
View/report issues

Topics

#handle #error #state #quality

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

equatable, flutter

More

Packages that depend on doso