.claude/skills/implement-data/SKILL.md
Implements the Flutter data layer (Models, DataSources, Repository Implementations) following Clean Architecture. Use whenever creating or modifying files in lib/data/**, implementing an API call, serializing/deserializing JSON, adding a local cache, creating a Repository implementation, or connecting a DataSource to a Repository. Covers Models extending Entity, DataSources returning raw data, and RepositoryImpl with try/catch and Result<T>. Activate even when the user says "call the API", "save to cache", "implement the repository", "parse the JSON response", or "add a data source" without explicitly mentioning Clean Architecture or data layer.
npx skillsauth add andrelucassvt/CleanMacForDevsWeb implement-dataInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
fromJson() com defaults, toJson(), e copyWith() retornando o Model.HttpResponse/Map/List) e NUNCA trate erros — deixe o Repository tratá-los.try/catch, converta para Model e retorne Result<T> — nunca throw.HttpService (não Dio diretamente) via construtor.lib/data/
├── models/
│ ├── user_model.dart
│ └── product_model.dart
├── datasources/
│ ├── user_remote_datasource.dart
│ └── user_local_datasource.dart
└── repositories/
├── user_repository_impl.dart
└── product_repository_impl.dart
import 'package:base_app/domain/entities/user_entity.dart';
class UserModel extends UserEntity {
const UserModel({
required super.id,
required super.name,
required super.email,
});
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
id: json['id'] as String? ?? '',
name: json['name'] as String? ?? '',
email: json['email'] as String? ?? '',
);
}
Map<String, dynamic> toJson() {
return {'id': id, 'name': name, 'email': email};
}
@override
UserModel copyWith({String? id, String? name, String? email}) {
return UserModel(
id: id ?? this.id,
name: name ?? this.name,
email: email ?? this.email,
);
}
factory UserModel.fromEntity(UserEntity entity) {
return UserModel(id: entity.id, name: entity.name, email: entity.email);
}
}
class HomeModel extends HomeEntity {
const HomeModel({required super.message, required super.items});
factory HomeModel.fromJson(Map<String, dynamic> json) {
return HomeModel(
message: json['message'] as String? ?? '',
items: (json['items'] as List<dynamic>?)
?.map((item) => item.toString())
.toList() ??
[], // ✅ Lista vazia como default
);
}
Map<String, dynamic> toJson() => {'message': message, 'items': items};
@override
HomeModel copyWith({String? message, List<String>? items}) {
return HomeModel(message: message ?? this.message, items: items ?? this.items);
}
}
class UserModel extends UserEntity {
const UserModel({required super.id, required super.name, required super.address});
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
id: json['id'] as String? ?? '',
name: json['name'] as String? ?? '',
address: json['address'] != null
? AddressModel.fromJson(json['address'] as Map<String, dynamic>)
: const AddressModel(street: '', city: '', zipCode: ''),
);
}
}
class UserModel extends UserEntityfromJson() com valores default — nunca json['id'] sem ?? ''toJson() implementadocopyWith() override retornando Model (não Entity)fromEntity() quando necessárioimport 'package:base_app/common/services/http/http_service.dart';
class UserRemoteDataSource {
const UserRemoteDataSource(this._httpService);
final HttpService _httpService;
Future<HttpResponse> getUsers() async {
return _httpService.get('/users');
}
Future<HttpResponse> getUserById(String id) async {
return _httpService.get('/users/$id');
}
Future<HttpResponse> createUser(Map<String, dynamic> userData) async {
return _httpService.post('/users', data: userData);
}
Future<HttpResponse> updateUser(String id, Map<String, dynamic> userData) async {
return _httpService.put('/users/$id', data: userData);
}
Future<HttpResponse> deleteUser(String id) async {
return _httpService.delete('/users/$id');
}
}
import 'dart:convert';
import 'package:base_app/common/services/storage_service.dart';
class UserLocalDataSource {
const UserLocalDataSource(this._storage);
final StorageService _storage;
static const String _userKey = 'user_data';
Future<void> saveUser(Map<String, dynamic> userData) async {
await _storage.setString(_userKey, jsonEncode(userData));
}
Future<Map<String, dynamic>?> getUser() async {
final jsonString = await _storage.getString(_userKey);
if (jsonString == null) return null;
return jsonDecode(jsonString) as Map<String, dynamic>;
}
Future<void> deleteUser() async => _storage.remove(_userKey);
Future<bool> hasUser() async => _storage.containsKey(_userKey);
}
HttpService, StorageService)HttpResponse, Map, List)import 'package:base_app/config/error/result_pattern.dart';
import 'package:base_app/domain/entities/user_entity.dart';
import 'package:base_app/domain/interfaces/user_repository.dart';
import 'package:base_app/data/datasources/user_remote_datasource.dart';
import 'package:base_app/data/models/user_model.dart';
class UserRepositoryImpl implements UserRepository {
const UserRepositoryImpl(this._remoteDataSource);
final UserRemoteDataSource _remoteDataSource;
@override
Future<Result<UserEntity>> getUserById(String id) async {
try {
final response = await _remoteDataSource.getUserById(id);
final model = UserModel.fromJson(response.data as Map<String, dynamic>);
return Result.ok(model);
} catch (e) {
return Result.error(Exception('Failed to get user: $e'));
}
}
@override
Future<Result<List<UserEntity>>> getAllUsers() async {
try {
final response = await _remoteDataSource.getUsers();
final users = (response.data as List<dynamic>)
.map((json) => UserModel.fromJson(json as Map<String, dynamic>))
.toList();
return Result.ok(users);
} catch (e) {
return Result.error(Exception('Failed to get users: $e'));
}
}
@override
Future<Result<UserEntity>> createUser(UserEntity user) async {
try {
final userData = UserModel.fromEntity(user).toJson();
final response = await _remoteDataSource.createUser(userData);
final model = UserModel.fromJson(response.data as Map<String, dynamic>);
return Result.ok(model);
} catch (e) {
return Result.error(Exception('Failed to create user: $e'));
}
}
@override
Future<Result<void>> deleteUser(String id) async {
try {
await _remoteDataSource.deleteUser(id);
return Result.ok(null);
} catch (e) {
return Result.error(Exception('Failed to delete user: $e'));
}
}
}
@override
Future<Result<UserEntity>> getUserById(String id) async {
try {
final response = await _remoteDataSource.getUserById(id);
final model = UserModel.fromJson(response.data as Map<String, dynamic>);
await _localDataSource.saveUser(model.toJson());
return Result.ok(model);
} catch (e) {
try {
final cachedData = await _localDataSource.getUser();
if (cachedData != null) {
return Result.ok(UserModel.fromJson(cachedData));
}
} catch (_) {}
return Result.error(Exception('Failed to get user: $e'));
}
}
implements UserRepositorytry/catchResult<T> — nunca lança exceçõeslib/data/models/<nome>_model.dartfromJson() com defaults (?? '', ?? [], ?? 0)toJson() implementadocopyWith() override retornando ModelfromEntity() se necessáriolib/data/datasources/<nome>_<tipo>_datasource.dartHttpService ou StorageService via construtorHttpResponse, Map ou Listlib/data/repositories/<nome>_repository_impl.darttry/catchResult<T> sempre| Erro | Correto |
|---|---|
| json['id'] sem cast/default | json['id'] as String? ?? '' |
| DataSource com try/catch | DataSource apenas retorna, Repository trata |
| Repository sem try/catch | SEMPRE envolva em try/catch |
| Future<UserEntity> no Repository | Future<Result<UserEntity>> |
Última atualização: 28 de março de 2026
testing
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy. Activate even when the user says 'create a skill for X', 'the skill is not triggering', 'improve this skill description', 'the agent is not using the skill', 'add a skill to teach the agent how to do X', 'this skill is wrong', or 'update the skill' without explicitly mentioning evals or benchmark.
development
Implements Flutter reusable widgets following the project architecture. Use whenever creating or modifying widgets in presentation/<feature>/widgets/, presentation/<feature>/content/, or common/widgets/. Covers StatelessWidget vs StatefulWidget decision, Entity as parameter, i18n, dispose, componentization rules, and when to access the Cubit via context.read. Activate even when the user says 'extract this to a widget', 'create a list item widget', 'build a reusable card', 'factor out this UI block', 'create a component for this', or 'this View is getting too big' without explicitly mentioning StatelessWidget or reusable components.
tools
Implements Flutter View screens following the project architecture. Use whenever creating or modifying a View (StatefulWidget + Cubit + BlocBuilder), adding a new screen, wiring up BlocBuilder/BlocConsumer/BlocListener, setting up SafeArea, or navigating from the View. Covers State, Cubit, View file, route, DI registration, and common mistakes. Activate even when the user just says "create a screen" or "add a new page", without explicitly mentioning Cubit or BLoC.
testing
Implements Flutter Cubit and State (View Model layer) following the project architecture. Use whenever creating or modifying a Cubit or State class, adding an async method to a Cubit, handling form submission or validation, implementing debounce search, managing loading/error/navigation states, or wiring a Cubit to a Repository or StorageService. Covers sealed States, async patterns with Result<T>, CRUD Cubits, local persistence via StorageService, navigation states, debounce, and common mistakes. Activate even when the user says "add a method", "handle the loading state", or "save locally" without explicitly mentioning Cubit or BLoC.