Small component that encapsulates an application's scenario logic.
[Changelog
](./CHANGELOG.md) | [License
](./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
x
Simple and easy to use APIx
Fully tested (100% coverage)x
Fully documentedx
sealed_result
compatible (see sealed_result)
Available usecase types:
Usecase<Input, Output>
NoParamsUsecase<Output>
ResultUsecase<Input, Output, Failure>
NoParamsResultUsecase<Output, Failure>
StreamUsecase<Input, Output>
NoParamsStreamUsecase<Output>
StreamResultUsecase<Input, Output, Failure>
NoParamsStreamResultUsecase<Output, Failure>
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
Future<int> execute(int params) async {
return params + params;
}
}
The execute
method is the one that will be called when you call the call
method on your usecase.
final usecase = AdditionUsecase();
final result = await usecase(2);
print(result); // 4
Checking preconditions
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<PreconditionsResult> checkPrecondition((int, int)? params) {
if (params == null) {
return PreconditionsResult(isValid: false, message: 'Params is null');
}
if (params.$2 == 0) {
return PreconditionsResult(isValid: false, message: 'Cannot divide by 0');
}
return PreconditionsResult(isValid: true);
}
@override
Future<double> execute((int, int) params) async {
return params.$1 / params.$2;
}
}
Using a result
You can use a result (see sealed_result) usecase to return a Result
object instead of a raw value:
class DivisionResultUsecase extends ResultUsecase<(int, int), double, Failure> {
const DivisionResultUsecase();
@override
FutureOr<PreconditionsResult> checkPrecondition((int, int)? params) {
if (params == null) {
return PreconditionsResult(isValid: false, message: 'Params is null');
}
if (params.$2 == 0) {
return PreconditionsResult(isValid: false, message: 'Cannot divide by 0');
}
return PreconditionsResult(isValid: true);
}
@override
Future<Result<double, Failure>> execute((int, int) params) async {
return Result.success(params.$1 / params.$2);
}
@override
Result<double, Failure> onException(UsecaseException e) =>
Result.failure(Failure(e.message ?? ''));
}
You need to override the onException
method to build the Failure
object from the UsecaseException
.
Using a stream
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++) {
yield i;
}
}
}
Libraries
- generic_usecase
- Small component that encapsulates an application's scenario logic.