Stats
Actions
Tags
From ufil
GM Freezed patterns — model, DTO, state, event, failure, params for Flutter Clean Architecture
How this skill is triggered — by the user, by Claude, or both
Slash command
/ufil:freezedThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
```dart
@Default, NO nullable@freezed
sealed class PropertyModel with _$PropertyModel {
const factory PropertyModel({
@Default('') String id,
@Default('') String name,
@Default('') String address,
@Default('') String type,
@Default(0) int totalUnits,
@Default(0) int occupiedUnits,
@Default(0) double monthlyRevenue,
}) = _PropertyModel;
}
Rules:
@Default() — no nullable ? fieldssealed class with _$ mixinfromJson/toJson — models are pure domain objectsconst factory only@JsonKey + .toModel()@freezed
sealed class PropertyDto with _$PropertyDto {
const PropertyDto._();
const factory PropertyDto({
@JsonKey(name: 'id') String? id,
@JsonKey(name: 'name') String? name,
@JsonKey(name: 'address') String? address,
@JsonKey(name: 'property_type') String? type,
@JsonKey(name: 'total_units') int? totalUnits,
@JsonKey(name: 'occupied_units') int? occupiedUnits,
@JsonKey(name: 'monthly_revenue') double? monthlyRevenue,
}) = _PropertyDto;
factory PropertyDto.fromJson(Map<String, dynamic> json) {
return _$PropertyDtoFromJson(json);
}
PropertyModel toModel() {
return PropertyModel(
id: id ?? '',
name: name ?? '',
address: address ?? '',
type: type ?? '',
totalUnits: totalUnits ?? 0,
occupiedUnits: occupiedUnits ?? 0,
monthlyRevenue: monthlyRevenue ?? 0,
);
}
}
Rules:
?@JsonKey(name: '...') on EVERY field — even when Dart name matches JSON keyconst PropertyDto._() needed for .toModel() methodfromJson + toModel() mapper?? values in toModel() to match model @Default@freezed abstract class, part of bloc// property_detail_event.dart
part of 'property_detail_bloc.dart';
@freezed
abstract class PropertyDetailEvent with _$PropertyDetailEvent {
const factory PropertyDetailEvent.init({required String propertyId}) = _InitEvent;
const factory PropertyDetailEvent.refresh() = _RefreshEvent;
const factory PropertyDetailEvent.delete() = _DeleteEvent;
}
Rules:
part of the bloc file (not a standalone file)@freezed abstract class (not sealed class).init() → _InitEvent_InitEvent, _RefreshEvent) for pattern matching in Bloc// property_detail_state.dart
part of 'property_detail_bloc.dart';
// Main state — composes sub-states
@freezed
abstract class PropertyDetailState with _$PropertyDetailState {
const factory PropertyDetailState({
@Default(GetPropertyDetailState.idle()) GetPropertyDetailState propertyDetailState,
@Default(DeletePropertyDetailState.idle()) DeletePropertyDetailState deletePropertyDetailState,
}) = _PropertyDetailState;
}
// Sub-state per async action — naming: {Action}{BlocName}State
// All variants share the SAME named params with defaults (uniform shape)
@freezed
abstract class GetPropertyDetailState with _$GetPropertyDetailState {
const factory GetPropertyDetailState.idle({
@Default(PropertyModel()) PropertyModel property,
@Default(Failure.noFailure()) Failure failure,
}) = _GetPropertyDetailIdleState;
const factory GetPropertyDetailState.loading({
@Default(PropertyModel()) PropertyModel property,
@Default(Failure.noFailure()) Failure failure,
}) = GetPropertyDetailLoadingState;
const factory GetPropertyDetailState.done({
@Default(PropertyModel()) PropertyModel property,
@Default(Failure.noFailure()) Failure failure,
}) = GetPropertyDetailDoneState;
const factory GetPropertyDetailState.error({
@Default(PropertyModel()) PropertyModel property,
@Default(Failure.noFailure()) Failure failure,
}) = GetPropertyDetailErrorState;
}
@freezed
abstract class DeletePropertyDetailState with _$DeletePropertyDetailState {
const factory DeletePropertyDetailState.idle({
@Default(Failure.noFailure()) Failure failure,
}) = _DeletePropertyDetailIdleState;
const factory DeletePropertyDetailState.loading({
@Default(Failure.noFailure()) Failure failure,
}) = DeletePropertyDetailLoadingState;
const factory DeletePropertyDetailState.done({
@Default(Failure.noFailure()) Failure failure,
}) = DeletePropertyDetailDoneState;
const factory DeletePropertyDetailState.error({
@Default(Failure.noFailure()) Failure failure,
}) = DeletePropertyDetailErrorState;
}
Rules:
part of the bloc file@freezed abstract class (not sealed class)isLoading, hasError) — use sub-state unions{Action}{BlocName}State (e.g., GetPropertyDetailState, DeletePropertyDetailState)@Default(Failure.noFailure()) Failure failure always; data fields like @Default(...) {Type} {field} for queries).idle() variant is private (_ prefix), .loading()/.done()/.error() are public@Default({SubState}.idle())state.copyWith(propertyDetailState: ...) in bloc handlers; pass named args (done(property: value), error(failure: error))// core/error/failures.dart
@freezed
sealed class Failure with _$Failure {
const Failure._();
const factory Failure.server({
@Default('Server error occurred') String message,
int? statusCode,
}) = ServerFailure;
const factory Failure.network([
@Default('No internet connection') String message,
]) = NetworkFailure;
const factory Failure.cache([
@Default('Cache error') String message,
]) = CacheFailure;
const factory Failure.auth([
@Default('Authentication error') String message,
]) = AuthFailure;
const factory Failure.unexpected([
@Default('An unexpected error occurred') String message,
]) = UnexpectedFailure;
}
// packages/domain/domain_common/lib/src/models/failure.dart
@freezed
class Failure with _$Failure {
const Failure._();
const factory Failure.noFailure() = NoFailure;
const factory Failure.unexpectedError({
@Default('An unexpected error occurred') String message,
}) = UnexpectedErrorFailure;
// Network
const factory Failure.timeout() = TimeoutFailure;
const factory Failure.noConnection() = NoConnectionFailure;
const factory Failure.serverFailure() = ServerFailure;
const factory Failure.requestFailure() = RequestFailure;
// Auth
const factory Failure.userNotLoggedIn() = UserNotLoggedInFailure;
const factory Failure.tokenExpired() = TokenExpiredFailure;
const factory Failure.unauthorized() = UnauthorizedFailure;
// App-Specific
const factory Failure.permissionDenied() = PermissionDeniedFailure;
const factory Failure.forceUpdateRequired() = ForceUpdateFailure;
const factory Failure.businessLogicFailure({
@Default('Business logic validation failed') String message,
}) = BusinessLogicFailure;
}
@freezed
sealed class AddPropertyParams with _$AddPropertyParams {
const factory AddPropertyParams({
required String name,
required String address,
required String type,
required String category,
@Default('') String description,
@Default('') String photoUrl,
}) = _AddPropertyParams;
}
Rules:
required for mandatory fields, @Default for optional| Type | Freezed? | Location |
|---|---|---|
| Model | Yes — @Default, no nullable | domain/models/ |
| DTO | Yes — nullable, @JsonKey, .toModel() | data/models/ |
| Bloc Event | Yes — named constructors | presentation/blocs/ |
| Bloc State | Yes — sub-state unions | presentation/blocs/ |
| Failure | Yes — union type | core/error/ or domain_common/models/ |
| Params | Yes — for complex args | domain/models/ |
| Enums | No — plain Dart enum | domain/models/ |
After adding/modifying freezed classes:
# Non-modular
dart run build_runner build --delete-conflicting-outputs
# Modular (all packages)
melos run build
# Modular (specific layer)
melos run generate:domain
Generated files: *.freezed.dart, *.g.dart — add to .gitignore or .claudeignore
${CLAUDE_PLUGIN_ROOT}/docs/DOMAIN_LAYER.md — Domain layer model patterns${CLAUDE_PLUGIN_ROOT}/docs/DATA_LAYER.md — Data layer DTO patterns${CLAUDE_PLUGIN_ROOT}/docs/BLOC_PATTERN.md — Bloc event and state patternsCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub ghozimahdi/gm-claude-plugins --plugin ufil