Flutter-clean-arch-skills flutter-guide
install
source · Clone the upstream repo
git clone https://github.com/aleksandr-chaika/flutter-clean-arch-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/aleksandr-chaika/flutter-clean-arch-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/flutter-guide" ~/.claude/skills/aleksandr-chaika-flutter-clean-arch-skills-flutter-guide && rm -rf "$T"
manifest:
skills/flutter-guide/SKILL.mdsource content
Flutter Guide — Clean Architecture + BLoC
Reference Guide
| Topic | Reference | Use When |
|---|---|---|
| Clean Architecture | | Layer rules, dependency direction |
| BLoC Patterns | | @freezed events/states, handlers, transformers |
| UI Guidelines | | AppColors, AppTextStyles, SVGImage |
| Review Checklist | | Code review, quality checks |
| Mocking Guide | | Mocktail, MockBloc, ProviderContainer |
| Test Patterns | | UseCase tests, BLoC tests, widget tests |
Project Structure
lib/ ├── core/ # AppError, extensions, DI (GetIt) ├── data/ │ ├── mapper/ # Entity <-> Model mappers │ ├── network/ # API client, models (@JsonSerializable), interceptors │ ├── repository/ # Repository implementations │ └── services/ ├── domain/ │ ├── entity/ # Domain entities (@freezed) │ ├── repository/ # Abstract interfaces │ ├── services/ │ └── usecases/ ├── presentation/ │ ├── {feature}/ │ │ ├── bloc.dart # FeatureBloc │ │ ├── events.dart # @freezed events │ │ ├── states.dart # @freezed states │ │ └── view/ # Pages + widgets │ └── common/ └── resources/ # Colors, text styles, localization
Dependency Rule
Presentation -> Domain <- Data — Domain depends on NOTHING.
Critical Rules
1. MANDATORY: @freezed for Events and States
@freezed class FeatureEvent with _$FeatureEvent { const factory FeatureEvent.started() = _Started; const factory FeatureEvent.loadData({required int id}) = _LoadData; } @freezed class FeatureState with _$FeatureState { const factory FeatureState.initial() = _Initial; const factory FeatureState.loading() = _Loading; const factory FeatureState.loaded(Data data) = _Loaded; const factory FeatureState.error(AppError error) = _Error; }
2. BLoC Individual Event Handlers
class FeatureBloc extends Bloc<FeatureEvent, FeatureState> { FeatureBloc() : super(const FeatureState.initial()) { on<_Started>(_onStarted); on<_LoadData>(_onLoadData); } Future<void> _onLoadData(_LoadData event, Emitter<FeatureState> emit) async { emit(const FeatureState.loading()); final result = await _repository.getData(event.id); result.fold( (error) => emit(FeatureState.error(error)), (data) => emit(FeatureState.loaded(data)), ); } }
NEVER use
event.when() or state.when() in BLoC handlers.
NEVER use a single handler for all events.
3. Either for Error Handling
abstract class UserRepository { Future<Either<AppError, User>> getUser(int id); }
4. Type-Safe Models
// Domain Entity @freezed class UserEntity with _$UserEntity { const factory UserEntity({ required String id, required String name, required UserRole role, }) = _UserEntity; } // API Model @freezed @JsonSerializable() class UserApiModel with _$UserApiModel { const factory UserApiModel({...}) = _UserApiModel; factory UserApiModel.fromJson(Map<String, dynamic> json) => _$UserApiModelFromJson(json); } // Mapper extension UserApiMapper on UserApiModel { UserEntity toDomain() => UserEntity(id: id, name: name, role: UserRole.fromString(role)); }
Dart 3.x: Use Modern Features
- Sealed classes for exhaustive state matching (alternative to @freezed unions)
- Pattern matching in switch expressions
- Records for lightweight multiple return values
- Null safety everywhere, no
operator without justification!
Performance Rules
- const constructors everywhere possible
- Keys on list items (
)ValueKey(item.id) - ListView.builder for large lists
- buildWhen in BlocBuilder for granular rebuilds
- CachedNetworkImage with
/memCacheWidthmemCacheHeight - Extract expensive subtrees into separate const widgets
- No expensive operations in
methodsbuild()
Anti-Patterns (FORBIDDEN)
| Pattern | Reason |
|---|---|
for data | No type safety |
in BLoC handlers | Use individual handlers |
in BLoC handlers | Only for UI |
| Single handler for all events | Hard to maintain |
| Hardcoded strings in UI | Use AppLocalization |
| Use SVGImage() |
| Redundant comments | Code must be self-documenting |
type | No type safety |
UI Patterns
// BlocBuilder with buildWhen BlocBuilder<FeatureBloc, FeatureState>( buildWhen: (previous, current) => previous != current, builder: (context, state) { return state.when( initial: () => const SizedBox.shrink(), loading: () => const LoadingWidget(), loaded: (data) => DataWidget(data: data), error: (error) => ErrorWidget(error: error), ); }, ) // BlocListener for side effects BlocListener<FeatureBloc, FeatureState>( listenWhen: (previous, current) => current is _Error, listener: (context, state) { if (state is _Error) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(state.error.message)), ); } }, )
Code Generation
flutter pub run build_runner build --delete-conflicting-outputs
Testing Summary
- BLoC tests (MANDATORY):
withbloc_test
, seed, expect, verifyblocTest<>() - Flow tests (MANDATORY): after create -> list updated, after delete -> item removed
- Widget tests: BlocProvider + MockBloc
- Coverage: 80%+ with
flutter test --coverage
Review Summary
- Architecture compliance (correct layer, inward dependencies)
- Type safety (no dynamic, no Map<String, dynamic>)
- BLoC patterns (individual handlers, no event.when(), no state.when())
- @freezed on all events/states
- UI standards (AppColors, AppTextStyles, AppLocalization, const)
- No redundant comments