Last updated:
0 purchases
generic usecase
Small component that encapsulates an application's scenario logic.
[Changelog] | [License]
Introduction #
Playing around with the clean architecture, I often found myself rewriting the generic code of my usecases.
These class enable you to encapsulate your logic in an atomic elements that you can then inject and use throughout your application.
Features #
✅ Simple and easy to use API
✅ Fully tested (100% coverage)
✅ Fully documented
Available usecase types:
Usecase<Input, Output>
NoParamsUsecase<Output>
StreamUsecase<Input, Output>
NoParamsStreamUsecase<Output>
Usage #
Simple usecase #
Let's say you want to add two numbers together, you can create a usecase like this:
class AdditionUsecase extends Usecase<int, int> {
const AdditionUsecase();
@override
FutureOr<int> execute(int params) async => params + params;
}
copied to clipboard
The execute method is the one that will be called when you call the call method on your usecase.
final addition = AdditionUsecase();
await addition(2).then(print, onError: print); // 4
copied to clipboard
Using a stream usecase #
You can use a stream usecase to return a Stream instead of a raw value:
class GeneratorUsecase extends NoParamsStreamUsecase<int> {
const GeneratorUsecase();
@override
Stream<int> execute() async* {
for (int i = 0; i < 10; i++) {
await Future<void>.delayed(const Duration(seconds: 1));
yield i;
}
}
}
copied to clipboard
You can then use it like this:
final generator = GeneratorUsecase();
final stream = generator();
stream.listen(
print,
onError: print,
onDone: () => print('Done'),
);
copied to clipboard
Checking preconditions and postconditions #
You can add a precondition check to your usecase, which will be executed before the execute method:
class DivisionUsecase extends Usecase<(int, int), double> {
const DivisionUsecase();
@override
FutureOr<ConditionsResult> checkPreconditions((int, int)? params) {
if (params == null) {
return ConditionsResult(isValid: false, message: 'Params is null');
}
if (params.$2 == 0) {
return ConditionsResult(isValid: false, message: 'Cannot divide by 0');
}
return ConditionsResult(isValid: true);
}
@override
FutureOr<double> execute((int, int) params) async => params.$1 / params.$2;
}
copied to clipboard
You can also add a postcondition check to your usecase, which will be executed after the execute method:
class AdditionUsecase extends Usecase<int, int> {
const AdditionUsecase();
@override
FutureOr<int> execute(int params) async => params + params;
@override
FutureOr<ConditionsResult> checkPostconditions(int? result) {
if (result == null) {
return ConditionsResult(isValid: false, message: 'Result is null');
}
if (result < 0) {
return ConditionsResult(isValid: false, message: 'Result is negative');
}
return ConditionsResult(isValid: true);
}
}
copied to clipboard
Catching exceptions #
You can catch exceptions thrown by your usecase by overriding the onException method:
class AdditionUsecase extends Usecase<int, int> {
const AdditionUsecase();
@override
FutureOr<int> execute(int params) async => params + params;
@override
FutureOr<int> onException(Object e) {
print(e); // Prints the exception
return super.onException(e);
}
}
copied to clipboard
This method will be called when an exception is thrown by the execute method. It will also be called when a precondition or postcondition check fails.
Using a Result #
By assembling the previous examples, you can create a usecase that returns a Result object. By catching exceptions and checking preconditions and postconditions, you can return a Result object that will be either a Success or a Failure :
This example uses the sealed_result package.
class DivisionResultUsecase extends Usecase<(int, int), Result<double, Failure>> {
const DivisionResultUsecase();
@override
FutureOr<ConditionsResult> checkPreconditions((int, int)? params) {
if (params == null) {
return ConditionsResult(isValid: false, message: 'Params is null');
}
if (params.$2 == 0) {
return ConditionsResult(isValid: false, message: 'Cannot divide by 0');
}
return ConditionsResult(isValid: true);
}
@override
FutureOr<Result<double, Failure>> execute((int, int) params) async =>
Result.success(params.$1 / params.$2);
@override
FutureOr<Result<double, Failure>> onException(Object e) {
if (e case UsecaseException _) {
return Result.failure(Failure(e.message ?? ''));
}
if (e case Exception || Error) {
return Result.failure(Failure(e.toString()));
}
return Result.failure(Failure(''));
}
}
copied to clipboard
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.