Creates a domain repository interface, one or more UseCases, and optionally a repository implementation wired to a data source. Use when the user wants to add a repository, use case, domain method, or wire a data source to the domain layer for a feature.
booking) and package name from pubspec.yaml (name: field).File: lib/src/features/<feature>/domain/repository/<feature>_repository.dart
import 'package:<package>/src/core/utils/typedef.dart';
// import entities as needed
abstract class <Feature>Repository {
FutureEither<ReturnType> methodName(ParamType param);
FutureEither<void> setMethodName(ParamType param);
}
Rules:
data/freezed_annotationFutureEither<T> or StreamEither<T> — never write Future<Either<...>> in fullabstract class only (no interface keyword needed)Add an event class to lib/src/features/<feature>/domain/analytics/<feature>_events.dart for each new UseCase:
final class <MethodName>UseCaseEvent extends AnalyticsEvent {
const <MethodName>UseCaseEvent({required super.name, super.properties});
factory <MethodName>UseCaseEvent.success({Map<String, dynamic>? properties}) =>
<MethodName>UseCaseEvent(name: '<method_name>_use_case_success', properties: properties);
factory <MethodName>UseCaseEvent.failure({required Map<String, dynamic> properties}) =>
<MethodName>UseCaseEvent(name: '<method_name>_use_case_failure', properties: properties);
}
Event name format: <method_name>_use_case_success / <method_name>_use_case_failure (snake_case).
File: lib/src/features/<feature>/domain/usecases/<verb>_<noun>_use_case.dart (no feature prefix in the file name — e.g. get_app_theme_mode_use_case.dart)
No parameters (NoParams):
import 'package:injectable/injectable.dart';
import 'package:<package>/src/core/monitoring/analytics/analytics.dart';
import 'package:<package>/src/core/monitoring/analytics/analytics_events.dart';
import 'package:<package>/src/core/usecases/use_case.dart';
import 'package:<package>/src/core/utils/typedef.dart';
import 'package:<package>/src/features/<feature>/domain/analytics/<feature>_events.dart';
import 'package:<package>/src/features/<feature>/domain/repository/<feature>_repository.dart';
@LazySingleton()
class <MethodName>UseCase extends UseCase<ReturnType, NoParams> {
final <Feature>Repository _repo;
<MethodName>UseCase(this._repo);
@override
FutureEither<ReturnType> call(NoParams params) async {
final result = await _repo.methodName();
return result.fold(
(failure) {
Analytics.track(<MethodName>UseCaseEvent.failure(properties: {
AnalyticsPropertyKeys.failureMessage: failure.message,
AnalyticsPropertyKeys.failureType: failure.type.name,
AnalyticsPropertyKeys.failureSource: failure.source,
}));
return result;
},
(_) {
Analytics.track(<MethodName>UseCaseEvent.success());
return result;
},
);
}
}
With parameters — define Params class in the same file, below the UseCase:
@LazySingleton()
class <MethodName>UseCase extends UseCase<ReturnType, <MethodName>Params> {
// ... same body, use params.<field> when calling _repo
}
class <MethodName>Params {
const <MethodName>Params({required this.fieldName});
final FieldType fieldName;
}
Naming rules:
<MethodName>UseCase — suffix is UseCase (capital C), never Usecase_use_case.dart — never usecase as one word; do not repeat the feature slug in the filenameFile: lib/src/features/<feature>/data/repository/<feature>_repository_impl.dart
import 'package:dartz/dartz.dart';
import 'package:injectable/injectable.dart';
import 'package:<package>/src/core/error/error.dart';
import 'package:<package>/src/core/utils/typedef.dart';
import 'package:<package>/src/features/<feature>/data/datasource/<feature>_local_data_source.dart';
import 'package:<package>/src/features/<feature>/data/datasource/<feature>_remote_data_source.dart';
import 'package:<package>/src/features/<feature>/domain/repository/<feature>_repository.dart';
@Singleton(as: <Feature>Repository)
class <Feature>RepositoryImpl implements <Feature>Repository {
final <Feature>LocalDataSource _localDataSource;
final <Feature>RemoteDataSource _remoteDataSource;
<Feature>RepositoryImpl(this._localDataSource, this._remoteDataSource);
@override
FutureEither<ReturnType> methodName(ParamType param) async {
try {
final result = await _localDataSource.methodName(param: param);
return Right(result);
} on Exception catch (e) {
return Left(e.toFailure(source: '$runtimeType.methodName'));
}
}
}
Rules:
@Singleton(as: <Feature>Repository) — binds impl to the interfacetry/on Exception catch (e) → e.toFailure(source: '$runtimeType.<methodName>')Right(value) on success, Left(e.toFailure(...)) on failureFailure.* variants manuallyGetIt.instance inside the classdomain/repository/ — returns FutureEither<T>domain/analytics/<feature>_events.dart*UseCase in file *_use_case.dartParams class defined below UseCase in same file (if not NoParams)@Singleton(as: Interface)e.toFailure(source: '$runtimeType.method')dart run build_runner build -d after adding @LazySingleton / @Singleton