cool_bedrock
A bedrock package providing the blueprints and abstract definitions necessary to build a scalable and maintainable Dart/Flutter application.
β¨ Features
- ποΈ Formalized Architecture: Strict base contracts for all layers: Entity, Params, AppService, and Codable.
- π― Domain Logic (Use Cases): Typified UseCase hierarchy for commands and queries, including assisted flow management (UseCaseHandler).
- π‘οΈ Functional Error Handling: Leverages the functional types Either<Failure, T> and Option
- π Typed Errors: Coherent error structure using sealed base classes: Issue, Failure (business logic), RepositoryError, and DataSourceException (technical/infrastructure).
- π Reactive Services: Base classes for creating services that manage state using BehaviorSubject, PublishSubject, and periodic execution logic (Timer).
- π§ͺ Immutability & Testability: All core domain structures (Entity, Params) are immutable and comparable (Equatable).
π Installation
Requires Dart >=3.13.0.
Add the package to your pubspec.yaml:
dependencies:
cool_bedrock: ^3.0.0
Then run:
dart pub get
π Usage
Basic usage
import 'package:cool_bedrock/cool_bedrock.dart';
1. Creating the Use Case
This example demonstrates how to implement a UseCase, defining its specific Failure and parameter validation:
// 1. Define the domain types: params, entity and the raw remote model.
final class FetchUserParams extends Params {
const FetchUserParams({required this.userId});
final String userId;
// Checked automatically before executing the usecase.
@override
bool get isValid => userId.isNotEmpty;
@override
List<Object?> get props => [userId];
}
final class UserRemote {
const UserRemote({this.name});
final String? name;
UserEntity toEntity() => UserEntity(name: name ?? 'Unknown');
}
final class UserEntity extends Entity {
const UserEntity({required this.name});
final String name;
@override
List<Object?> get props => [name];
}
// In this example the repository returns a Future<Either<Failure, UserRemote>>.
// 2. Define the specific Failure for this domain
sealed class FetchUserFailure extends Failure {
const FetchUserFailure({super.message});
}
final class InvalidUserFailure extends FetchUserFailure {
const InvalidUserFailure() : super(message: 'Invalid User ID provided.');
@override
List<Object?> get props => [message];
}
final class InvalidParamsUserFailure extends FetchUserFailure {
const InvalidParamsUserFailure()
: super(message: 'Invalid parameters provided.');
@override
List<Object?> get props => [message];
}
// 3. Implement the UseCase contract
final class FetchUserUseCase
extends UseCase<UserEntity, FetchUserParams, FetchUserFailure> {
const FetchUserUseCase(this.repository);
final UserRepository repository;
// Called automatically if params.isNotValid is true.
@override
FetchUserFailure onInvalidParams() => const InvalidParamsUserFailure();
@override
Future<Either<FetchUserFailure, UserEntity>> execute(
FetchUserParams params,
) async {
// Core logic goes here. Mappers and Repositories are typically called here.
try {
final result = await repository.fetch(params.userId);
return result.fold(
// Map the repository failure to a domain failure
(failure) => const Left(InvalidUserFailure()),
// Map the remote model to the entity
(remote) => Right(remote.toEntity()),
); // Success
} catch (e) {
// Map low-level errors to high-level domain failures
return const Left(InvalidUserFailure()); // Failure
}
}
}
// 4. Or use the Handler: obtain -> transform -> map errors
final class FetchUserUseCaseHandle
extends UseCaseHandler<
UserEntity,
FetchUserParams,
FetchUserFailure,
UserRemote
> {
const FetchUserUseCaseHandle({required this.repository});
final UserRepository repository;
// Called automatically if params.isNotValid is true.
@override
FetchUserFailure onInvalidParams() => const InvalidParamsUserFailure();
// Obtain repository values. Multiple repositories can be called.
@override
FutureOr<UserRemote> obtainValues(
Resolver<FetchUserFailure> $,
FetchUserParams params,
) async {
// getValue unwraps a Future<Either<Issue, VALUE>> and maps its errors.
return await $(getValue(() => repository.fetch(params.userId)));
}
@override
UserEntity transformation(UserRemote values) {
if (values.name == null || values.name!.isEmpty) {
throw const UsecaseException(InvalidUserFailure());
}
// It can throw any exception, it will be handled by [wrapError].
return values.toEntity();
}
@override
FetchUserFailure wrapError(Object error, StackTrace stackTrace) {
return const InvalidUserFailure();
}
}
2. Execution and Error Handling
// Execution with valid parameters
const validParams = FetchUserParams(userId: 'user_123');
final validResult = await fetchUserUsecase.call(validParams);
validResult.fold(
// LEFT side (Failure)
(failure) => print('Error: ${failure.message}'),
// RIGHT side (Success)
(user) => print('Fetched User: ${user.name}'),
);
π‘ Reactive Services Example
The base services provide lifecycle control and reactivity. Here's a service that periodically updates a counter:
import 'package:cool_bedrock/cool_bedrock.dart';
import 'dart:async';
final class HeartbeatService extends TimerAndBehaviorService<int> {
HeartbeatService()
: super(periodicDuration: const Duration(seconds: 10));
int _counter = 0;
@override
Future<void> work() async {
// Logic that runs every 10 seconds
_counter++;
// Emit the new value to all subscribers
add(_counter);
}
// start(), stop(), and dispose() logic is inherited and controlled externally.
}
ποΈ Use Case Types Comparison
Choosing the right base class ensures your business logic is expressive and safe. Use this table as a quick guide to decide which one fits your needs:
| Use Case Type | Success Return | Error Return | Best For... |
|---|---|---|---|
UseCase |
Right(Entity) |
Left(Failure) |
Standard business logic with manual error mapping. |
UseCaseHandler |
Right(Entity) |
Left(Failure) |
Business logic with data fetching, transformation and error mapping. |
OneWayUseCase |
Some(Entity) |
None |
Queries where the absence of a value is a valid result (e.g., Search). |
OneWayFailureUseCase |
None (Success) |
Some(Failure) |
Standalone validations and guard checks (e.g., Is email taken?). |
π API Reference
Check the full API reference, including all generic types and abstract classes, on pub.flutter-io.cn β cool_bedrock.
Authors & Maintainers
This project was created and is primarily maintained by:
π€ Contributing
Contributions are welcome!
- Open issues for bugs or feature requests
- Fork the repo and submit a PR
- Run
dart formatanddart testbefore submitting
π§ͺ Testing
To run tests and see code coverage:
dart test
π License
MIT Β© 2025 Coolosos