skills/flutter/code-generation/SKILL.md
# 🔧 Skill: Code Generation Workflows ## 📋 Metadata | Atributo | Valor | |----------|-------| | **ID** | `flutter-code-generation` | | **Nivel** | 🟢 Básico | | **Versión** | 1.0.0 | | **Keywords** | `code-gen`, `build-runner`, `freezed`, `json-serializable`, `injectable`, `auto-route` | | **Referencia** | [build_runner Package](https://pub.dev/packages/build_runner), [Context7 MCP](https://github.com/upstash/context7) | ## 🔑 Keywords para Invocación Usa cualquiera de estos keywords en tus
npx skillsauth add altrupets/monorepo skills/flutter/code-generationInstall 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.
| Atributo | Valor |
|----------|-------|
| ID | flutter-code-generation |
| Nivel | 🟢 Básico |
| Versión | 1.0.0 |
| Keywords | code-gen, build-runner, freezed, json-serializable, injectable, auto-route |
| Referencia | build_runner Package, Context7 MCP |
Usa cualquiera de estos keywords en tus prompts para invocar este skill:
code-genbuild-runnerfreezedjson-serializableinjectableauto-routecode-generation@skill:code-generationConfigura code generation con freezed y json_serializable
Implementa dependency injection con injectable y code generation
@skill:code-generation - Setup completo de build_runner para el proyecto
Necesito generar modelos inmutables con freezed y serialización JSON
Configura auto_route para navigation con code generation
Code Generation en Flutter automatiza la creación de código boilerplate, reduciendo errores, mejorando type safety y aumentando dramáticamente la productividad del desarrollador. Este skill cubre las herramientas principales del ecosistema Flutter para generación de código.
⚠️ IMPORTANTE: Todos los comandos de este skill deben ejecutarse desde la raíz del proyecto (donde existe el directorio mobile/). El skill incluye verificaciones para asegurar que se está en el directorio correcto antes de ejecutar cualquier comando.
⚠️ IMPORTANTE: Todos los comandos de este skill deben ejecutarse desde la raíz del proyecto (donde existe el directorio mobile/). El skill incluye verificaciones para asegurar que se está en el directorio correcto antes de ejecutar cualquier comando.
my_app/
├── lib/
│ ├── models/
│ │ ├── user.dart # Freezed + JSON
│ │ ├── user.freezed.dart # Generated
│ │ ├── user.g.dart # Generated
│ │ ├── product.dart
│ │ ├── product.freezed.dart
│ │ └── product.g.dart
│ │
│ ├── api/
│ │ ├── api_response.dart # Generic response
│ │ ├── api_response.freezed.dart
│ │ └── api_response.g.dart
│ │
│ ├── routes/
│ │ ├── app_router.dart # auto_route config
│ │ └── app_router.gr.dart # Generated routes
│ │
│ ├── di/
│ │ ├── injection.dart # Injectable config
│ │ └── injection.config.dart # Generated DI
│ │
│ ├── core/
│ │ ├── converters/
│ │ │ ├── date_time_converter.dart
│ │ │ └── enum_converter.dart
│ │ └── modules/
│ │ └── third_party_module.dart
│ │
│ └── main.dart
│
├── test/
│ └── models/
│ └── user_test.dart
│
├── build.yaml # Build runner config
├── pubspec.yaml
└── analysis_options.yaml
name: my_app
description: A Flutter app with code generation
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
# Code Generation - Runtime dependencies
freezed_annotation: ^2.4.1
json_annotation: ^4.8.1
injectable: ^2.3.2
get_it: ^7.6.4
auto_route: ^7.8.4
# Utilities
equatable: ^2.0.5 # Optional: for non-freezed classes
dev_dependencies:
flutter_test:
sdk: flutter
# Code Generation - Dev dependencies
build_runner: ^2.4.6
freezed: ^2.4.5
json_serializable: ^6.7.1
injectable_generator: ^2.4.1
auto_route_generator: ^7.3.2
# Linting
flutter_lints: ^3.0.0
# Agregar dependencias
flutter pub add freezed_annotation json_annotation injectable get_it auto_route
flutter pub add dev:build_runner dev:freezed dev:json_serializable dev:injectable_generator dev:auto_route_generator
# ⚠️ IMPORTANTE: Ejecutar desde la raíz del proyecto (donde está mobile/)
if [ ! -d "mobile" ]; then
echo "Error: Ejecuta este comando desde la raíz del proyecto"
exit 1
fi
# Generar código (one-time)
cd mobile
flutter pub run build_runner build
cd ..
# Generar con limpieza de conflictos
cd mobile
flutter pub run build_runner build --delete-conflicting-outputs
cd ..
# Watch mode (regenera automáticamente)
cd mobile
flutter pub run build_runner watch --delete-conflicting-outputs
cd ..
# Limpiar archivos generados
cd mobile
flutter pub run build_runner clean
cd ..
# build.yaml (raíz del proyecto)
targets:
$default:
builders:
# Freezed
freezed:
enabled: true
options:
# Genera métodos copyWith, toString, ==, hashCode
copy_with: true
equal: true
to_string: true
# JSON Serializable
json_serializable:
enabled: true
options:
# Configuración global
any_map: false
checked: true
create_factory: true
create_to_json: true
disallow_unrecognized_keys: false
explicit_to_json: true
field_rename: none
generic_argument_factories: false
ignore_unannotated: false
# Injectable
injectable_generator:injectable_builder:
enabled: true
options:
auto_register: true
# Auto Route
auto_route_generator:
enabled: true
options:
# Configuración de rutas
routes_class_name: AppRouter
# Optimización de performance
global_options:
# Cache builds
build_cache:
enabled: true
# Builders a ejecutar
runs_before:
- freezed
- json_serializable
- injectable_generator
- auto_route_generator
# build_runner
*.g.dart
*.freezed.dart
*.gr.dart
*.config.dart
# Build cache
.dart_tool/
build/
# Generated files (opcional: commitear para CI/CD más rápido)
# Descomentar si NO quieres commitear archivos generados
# **/*.g.dart
# **/*.freezed.dart
# **/*.gr.dart
// lib/models/user.dart
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
@freezed
class User with _$User {
const factory User({
required String id,
required String name,
required String email,
String? avatarUrl,
@Default(false) bool isVerified,
@Default([]) List<String> roles,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
// Uso:
void main() {
// Crear instancia
final user = User(
id: '1',
name: 'John Doe',
email: '[email protected]',
);
// copyWith (inmutable)
final updatedUser = user.copyWith(name: 'Jane Doe');
// Equality (automático)
print(user == updatedUser); // false
print(user == user.copyWith()); // true
// toString (automático)
print(user); // User(id: 1, name: John Doe, ...)
// JSON serialization
final json = user.toJson();
final fromJson = User.fromJson(json);
}
// lib/models/api_result.dart
import 'package:freezed_annotation/freezed_annotation.dart';
part 'api_result.freezed.dart';
@freezed
class ApiResult<T> with _$ApiResult<T> {
const factory ApiResult.success(T data) = Success<T>;
const factory ApiResult.error(String message, {int? code}) = Error<T>;
const factory ApiResult.loading() = Loading<T>;
}
// Uso con pattern matching:
void handleResult(ApiResult<User> result) {
result.when(
success: (user) => print('User: ${user.name}'),
error: (message, code) => print('Error $code: $message'),
loading: () => print('Loading...'),
);
// O con map
final message = result.map(
success: (value) => 'Success: ${value.data}',
error: (err) => 'Error: ${err.message}',
loading: (_) => 'Loading...',
);
// O con maybeWhen (con default)
result.maybeWhen(
success: (user) => print('Got user: ${user.name}'),
orElse: () => print('Not success'),
);
}
// lib/models/product.dart
import 'package:freezed_annotation/freezed_annotation.dart';
part 'product.freezed.dart';
part 'product.g.dart';
@freezed
class Product with _$Product {
const Product._(); // Private constructor para custom methods
const factory Product({
required String id,
required String name,
required double price,
@Default(0) int stock,
@Default([]) List<String> images,
DateTime? createdAt,
}) = _Product;
factory Product.fromJson(Map<String, dynamic> json) =>
_$ProductFromJson(json);
// Custom getters
bool get isAvailable => stock > 0;
bool get isNew {
if (createdAt == null) return false;
final daysSinceCreation = DateTime.now().difference(createdAt!).inDays;
return daysSinceCreation <= 30;
}
String get displayPrice => '\$${price.toStringAsFixed(2)}';
String get mainImage => images.isNotEmpty ? images.first : '';
// Custom methods
Product decrementStock([int amount = 1]) {
return copyWith(stock: (stock - amount).clamp(0, stock));
}
Product addImage(String imageUrl) {
return copyWith(images: [...images, imageUrl]);
}
}
// lib/core/converters/date_time_converter.dart
import 'package:json_annotation/json_annotation.dart';
class DateTimeConverter implements JsonConverter<DateTime, String> {
const DateTimeConverter();
@override
DateTime fromJson(String json) => DateTime.parse(json);
@override
String toJson(DateTime object) => object.toIso8601String();
}
class TimestampConverter implements JsonConverter<DateTime, int> {
const TimestampConverter();
@override
DateTime fromJson(int json) => DateTime.fromMillisecondsSinceEpoch(json);
@override
int toJson(DateTime object) => object.millisecondsSinceEpoch;
}
// lib/models/order.dart
import 'package:freezed_annotation/freezed_annotation.dart';
import '../core/converters/date_time_converter.dart';
part 'order.freezed.dart';
part 'order.g.dart';
@freezed
class Order with _$Order {
const factory Order({
required String id,
required double total,
@DateTimeConverter() required DateTime createdAt,
@TimestampConverter() DateTime? completedAt,
}) = _Order;
factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);
}
// lib/models/address.dart
import 'package:freezed_annotation/freezed_annotation.dart';
part 'address.freezed.dart';
part 'address.g.dart';
@freezed
class Address with _$Address {
const factory Address({
required String street,
required String city,
required String country,
String? zipCode,
}) = _Address;
factory Address.fromJson(Map<String, dynamic> json) =>
_$AddressFromJson(json);
}
// lib/models/user_profile.dart
import 'package:freezed_annotation/freezed_annotation.dart';
import 'address.dart';
part 'user_profile.freezed.dart';
part 'user_profile.g.dart';
@freezed
class UserProfile with _$UserProfile {
const factory UserProfile({
required String userId,
required String name,
Address? address, // Nested object
@Default([]) List<Address> addresses, // List of objects
}) = _UserProfile;
factory UserProfile.fromJson(Map<String, dynamic> json) =>
_$UserProfileFromJson(json);
}
// JSON example:
// {
// "userId": "123",
// "name": "John",
// "address": {
// "street": "Main St",
// "city": "NYC",
// "country": "USA"
// },
// "addresses": [...]
// }
// lib/models/simple_model.dart
import 'package:json_annotation/json_annotation.dart';
part 'simple_model.g.dart';
@JsonSerializable()
class SimpleModel {
final String id;
final String name;
@JsonKey(name: 'email_address') // Map to different JSON key
final String email;
@JsonKey(includeIfNull: false) // Omit if null
final String? phone;
@JsonKey(defaultValue: false) // Default value
final bool isActive;
SimpleModel({
required this.id,
required this.name,
required this.email,
this.phone,
this.isActive = false,
});
factory SimpleModel.fromJson(Map<String, dynamic> json) =>
_$SimpleModelFromJson(json);
Map<String, dynamic> toJson() => _$SimpleModelToJson(this);
}
// lib/core/converters/enum_converter.dart
import 'package:json_annotation/json_annotation.dart';
enum UserRole {
admin,
user,
guest,
}
class UserRoleConverter implements JsonConverter<UserRole, String> {
const UserRoleConverter();
@override
UserRole fromJson(String json) {
return UserRole.values.firstWhere(
(role) => role.name.toLowerCase() == json.toLowerCase(),
orElse: () => UserRole.guest,
);
}
@override
String toJson(UserRole object) => object.name;
}
// Uso:
@JsonSerializable()
class User {
final String id;
@UserRoleConverter()
final UserRole role;
User({required this.id, required this.role});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
// lib/models/paginated_response.dart
import 'package:json_annotation/json_annotation.dart';
part 'paginated_response.g.dart';
@JsonSerializable(genericArgumentFactories: true)
class PaginatedResponse<T> {
final List<T> data;
final int page;
final int totalPages;
final int totalItems;
PaginatedResponse({
required this.data,
required this.page,
required this.totalPages,
required this.totalItems,
});
factory PaginatedResponse.fromJson(
Map<String, dynamic> json,
T Function(Object? json) fromJsonT,
) =>
_$PaginatedResponseFromJson(json, fromJsonT);
Map<String, dynamic> toJson(Object Function(T value) toJsonT) =>
_$PaginatedResponseToJson(this, toJsonT);
}
// Uso:
final response = PaginatedResponse<Product>.fromJson(
jsonData,
(json) => Product.fromJson(json as Map<String, dynamic>),
);
// lib/di/injection.dart
import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'injection.config.dart';
final getIt = GetIt.instance;
@InjectableInit(
initializerName: 'init',
preferRelativeImports: true,
asExtension: true,
)
void configureDependencies() => getIt.init();
// lib/main.dart
void main() {
configureDependencies();
runApp(MyApp());
}
// lib/services/api_service.dart
import 'package:injectable/injectable.dart';
import 'package:dio/dio.dart';
@injectable
class ApiService {
final Dio dio;
ApiService(this.dio);
Future<List<User>> getUsers() async {
final response = await dio.get('/users');
return (response.data as List)
.map((json) => User.fromJson(json))
.toList();
}
}
// lib/repositories/user_repository.dart
@injectable
class UserRepository {
final ApiService apiService;
UserRepository(this.apiService);
Future<User> getUserById(String id) async {
// Implementation
}
}
// lib/blocs/user_bloc.dart
@injectable
class UserBloc {
final UserRepository repository;
UserBloc(this.repository);
}
// lib/services/analytics_service.dart
@singleton // Single instance throughout app lifecycle
class AnalyticsService {
AnalyticsService() {
print('AnalyticsService created');
}
void logEvent(String event) {
print('Event: $event');
}
}
// lib/services/cache_service.dart
@lazySingleton // Created only when first accessed
class CacheService {
CacheService() {
print('CacheService created lazily');
}
Future<void> cache(String key, dynamic value) async {
// Implementation
}
}
// lib/services/http_service.dart
@Named('authenticated')
@injectable
class AuthenticatedHttpService {
// Implementation with auth
}
@Named('public')
@injectable
class PublicHttpService {
// Implementation without auth
}
// Uso:
@injectable
class MyService {
final AuthenticatedHttpService authHttp;
final PublicHttpService publicHttp;
MyService(
@Named('authenticated') this.authHttp,
@Named('public') this.publicHttp,
);
}
// lib/di/modules/third_party_module.dart
import 'package:injectable/injectable.dart';
import 'package:dio/dio.dart';
import 'package:shared_preferences/shared_preferences.dart';
@module
abstract class ThirdPartyModule {
@lazySingleton
Dio get dio => Dio(
BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 30),
),
);
@preResolve // Async initialization
Future<SharedPreferences> get prefs => SharedPreferences.getInstance();
}
// lib/main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await configureDependencies(); // Await para preResolve
runApp(MyApp());
}
// lib/services/logger_service.dart
@dev
@injectable
class DebugLoggerService implements LoggerService {
@override
void log(String message) => print('[DEBUG] $message');
}
@prod
@injectable
class ProductionLoggerService implements LoggerService {
@override
void log(String message) {
// Send to remote logging service
}
}
// lib/di/injection.dart
@InjectableInit(
initializerName: 'init',
preferRelativeImports: true,
asExtension: true,
)
void configureDependencies(String environment) =>
getIt.init(environment: environment);
// lib/main.dart
void main() {
const environment = String.fromEnvironment('ENV', defaultValue: 'dev');
configureDependencies(environment);
runApp(MyApp());
}
// Run with environment:
// flutter run --dart-define=ENV=prod
// lib/routes/app_router.dart
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import '../screens/home_screen.dart';
import '../screens/profile_screen.dart';
import '../screens/product_detail_screen.dart';
part 'app_router.gr.dart';
@AutoRouterConfig()
class AppRouter extends _$AppRouter {
@override
List<AutoRoute> get routes => [
AutoRoute(page: HomeRoute.page, initial: true),
AutoRoute(page: ProfileRoute.page),
AutoRoute(page: ProductDetailRoute.page),
];
}
// lib/main.dart
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final _appRouter = AppRouter();
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _appRouter.config(),
);
}
}
// lib/screens/home_screen.dart
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: Center(
child: ElevatedButton(
onPressed: () {
context.router.push(const ProfileRoute());
},
child: const Text('Go to Profile'),
),
),
);
}
}
// lib/screens/product_detail_screen.dart
@RoutePage()
class ProductDetailScreen extends StatelessWidget {
final String productId;
const ProductDetailScreen({
super.key,
@PathParam('id') required this.productId,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Product $productId')),
body: Center(child: Text('Details for $productId')),
);
}
}
// lib/routes/app_router.dart
@AutoRouterConfig()
class AppRouter extends _$AppRouter {
@override
List<AutoRoute> get routes => [
AutoRoute(
page: MainRoute.page,
initial: true,
children: [
AutoRoute(page: HomeRoute.page, initial: true),
AutoRoute(page: SearchRoute.page),
AutoRoute(page: ProfileRoute.page),
],
),
AutoRoute(page: ProductDetailRoute.page),
AutoRoute(page: SettingsRoute.page),
];
}
// lib/screens/main_screen.dart
@RoutePage()
class MainScreen extends StatelessWidget {
const MainScreen({super.key});
@override
Widget build(BuildContext context) {
return AutoTabsScaffold(
routes: const [
HomeRoute(),
SearchRoute(),
ProfileRoute(),
],
bottomNavigationBuilder: (_, tabsRouter) {
return BottomNavigationBar(
currentIndex: tabsRouter.activeIndex,
onTap: tabsRouter.setActiveIndex,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
);
},
);
}
}
// lib/routes/guards/auth_guard.dart
import 'package:auto_route/auto_route.dart';
class AuthGuard extends AutoRouteGuard {
@override
void onNavigation(NavigationResolver resolver, StackRouter router) {
final isAuthenticated = checkAuth(); // Your auth logic
if (isAuthenticated) {
resolver.next(true);
} else {
resolver.redirect(const LoginRoute());
}
}
bool checkAuth() {
// Check if user is authenticated
return false;
}
}
// lib/routes/app_router.dart
@AutoRouterConfig()
class AppRouter extends _$AppRouter {
@override
List<AutoRoute> get routes => [
AutoRoute(page: LoginRoute.page),
AutoRoute(
page: ProfileRoute.page,
guards: [AuthGuard()], // Protected route
),
];
}
// lib/models/user.dart
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
@freezed
class User with _$User {
const User._();
const factory User({
required String id,
required String name,
required String email,
String? avatarUrl,
@Default(UserRole.user) UserRole role,
@Default([]) List<String> permissions,
DateTime? lastLoginAt,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
// Custom getters
bool get isAdmin => role == UserRole.admin;
bool get hasRecentActivity {
if (lastLoginAt == null) return false;
return DateTime.now().difference(lastLoginAt!).inDays < 7;
}
// Custom methods
bool hasPermission(String permission) {
return permissions.contains(permission) || isAdmin;
}
}
enum UserRole {
admin,
user,
guest,
}
// lib/services/user_service.dart
import 'package:injectable/injectable.dart';
import '../models/user.dart';
import 'api_service.dart';
@injectable
class UserService {
final ApiService _apiService;
UserService(this._apiService);
Future<List<User>> getUsers() async {
final response = await _apiService.get('/users');
return (response.data as List)
.map((json) => User.fromJson(json))
.toList();
}
Future<User> getUserById(String id) async {
final response = await _apiService.get('/users/$id');
return User.fromJson(response.data);
}
Future<User> updateUser(User user) async {
final response = await _apiService.put(
'/users/${user.id}',
data: user.toJson(),
);
return User.fromJson(response.data);
}
}
// Uso en BLoC/Cubit:
@injectable
class UserBloc extends Bloc<UserEvent, UserState> {
final UserService _userService;
UserBloc(this._userService) : super(UserInitial());
}
# 1. Crear proyecto
flutter create my_app
cd my_app
# 2. Agregar dependencias
flutter pub add freezed_annotation json_annotation injectable get_it auto_route
flutter pub add dev:build_runner dev:freezed dev:json_serializable dev:injectable_generator dev:auto_route_generator
# 3. Crear estructura
mkdir -p lib/{models,services,routes,di,screens}
# 4. Crear archivos base
# - lib/di/injection.dart
# - lib/routes/app_router.dart
# - lib/models/user.dart
# Verificar que estamos en la raíz del proyecto
if [ ! -d "mobile" ]; then
echo "Error: Ejecuta este comando desde la raíz del proyecto"
exit 1
fi
# 5. Generar código
cd mobile
flutter pub run build_runner build --delete-conflicting-outputs
cd ..
# 6. Ejecutar en watch mode durante desarrollo
cd mobile
flutter pub run build_runner watch --delete-conflicting-outputs
cd ..
✅ DO:
lib/
├── models/ # Todos los modelos juntos
│ ├── user.dart
│ └── product.dart
├── di/ # DI configuration
│ └── injection.dart
└── routes/ # Navigation
└── app_router.dart
❌ DON'T:
lib/
└── features/
├── auth/
│ └── user.dart # ❌ Fragmentado
└── products/
└── product.dart # ❌ Dificulta generación
Opción A: Commitear archivos generados ✅ Ventajas:
❌ Desventajas:
Opción B: NO commitear (usar .gitignore) ✅ Ventajas:
❌ Desventajas:
Recomendación: Commitear en proyectos pequeños/medianos, NO commitear en grandes con muchos developers.
# Verificar que estamos en la raíz del proyecto
if [ ! -d "mobile" ]; then
echo "Error: Ejecuta este comando desde la raíz del proyecto"
exit 1
fi
# Terminal 1: Watch mode
cd mobile
flutter pub run build_runner watch --delete-conflicting-outputs
cd ..
# Terminal 2: Hot reload app
cd mobile
flutter run
cd ..
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.35.0'
cache: true
- name: Get dependencies
working-directory: mobile
run: flutter pub get
- name: Generate code
working-directory: mobile
run: flutter pub run build_runner build --delete-conflicting-outputs
- name: Analyze
working-directory: mobile
run: flutter analyze
- name: Test
run: flutter test
# Verificar que estamos en la raíz del proyecto
if [ ! -d "mobile" ]; then
echo "Error: Ejecuta este comando desde la raíz del proyecto"
exit 1
fi
# Use cache entre builds
cd mobile
flutter pub run build_runner build --delete-conflicting-outputs
cd ..
# Para builds muy lentos, usa:
cd mobile
flutter pub run build_runner build --delete-conflicting-outputs --low-resources-mode
cd ..
# Limpia cache si hay problemas
cd mobile
flutter clean
flutter pub get
flutter pub run build_runner clean
flutter pub run build_runner build --delete-conflicting-outputs
cd ..
Usa Freezed cuando:
Usa manual cuando:
# Verificar que estamos en la raíz del proyecto
if [ ! -d "mobile" ]; then
echo "Error: Ejecuta este comando desde la raíz del proyecto"
exit 1
fi
# Solución: Usar --delete-conflicting-outputs
cd mobile
flutter pub run build_runner build --delete-conflicting-outputs
cd ..
// Verifica que la declaración part esté correcta:
part 'user.freezed.dart'; // ✅ Correcto
part 'user_freezed.dart'; // ❌ Incorrecto (sin punto)
# Verificar que estamos en la raíz del proyecto
if [ ! -d "mobile" ]; then
echo "Error: Ejecuta este comando desde la raíz del proyecto"
exit 1
fi
# 1. Limpiar cache
cd mobile
flutter clean
flutter pub get
cd ..
# 2. Usar low-resources-mode
cd mobile
flutter pub run build_runner build --low-resources-mode
cd ..
# 3. Reducir builders activos en build.yaml
// Si tienes conflictos de imports:
import 'package:freezed_annotation/freezed_annotation.dart' as freezed;
@freezed.freezed
class User with _$User {
// ...
}
# Verificar que estamos en la raíz del proyecto
if [ ! -d "mobile" ]; then
echo "Error: Ejecuta este comando desde la raíz del proyecto"
exit 1
fi
# Forzar regeneración
cd mobile
flutter pub run build_runner clean
flutter pub run build_runner build --delete-conflicting-outputs
cd ..
// Verifica que toJson use explicitToJson
@JsonSerializable(explicitToJson: true)
class User {
final Address address; // Nested object
// ...
}
# Makefile
.PHONY: generate watch clean get help
help:
@echo "Comandos disponibles:"
@echo " make generate - Generar código"
@echo " make watch - Watch mode"
@echo " make clean - Limpiar"
@echo " make get - Obtener dependencias"
get:
cd mobile && flutter pub get
generate: get
cd mobile && flutter pub run build_runner build --delete-conflicting-outputs
watch: get
cd mobile && flutter pub run build_runner watch --delete-conflicting-outputs
clean:
cd mobile && flutter clean
cd mobile && flutter pub get
cd mobile && flutter pub run build_runner clean
rebuild: clean generate
# Uso:
# make generate
# make watch
#!/bin/bash
# scripts/generate.sh
echo "🔧 Starting code generation..."
# Clean if flag passed
if [ "$1" == "--clean" ]; then
echo "🧹 Cleaning..."
flutter clean
flutter pub get
flutter pub run build_runner clean
fi
# Generate
echo "⚡ Generating code..."
flutter pub run build_runner build --delete-conflicting-outputs
echo "✅ Code generation complete!"
# Uso:
# ./scripts/generate.sh
# ./scripts/generate.sh --clean
// test/models/user_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/models/user.dart';
void main() {
group('User Model', () {
test('should create user from JSON', () {
final json = {
'id': '1',
'name': 'John Doe',
'email': '[email protected]',
};
final user = User.fromJson(json);
expect(user.id, '1');
expect(user.name, 'John Doe');
expect(user.email, '[email protected]');
});
test('should convert user to JSON', () {
const user = User(
id: '1',
name: 'John Doe',
email: '[email protected]',
);
final json = user.toJson();
expect(json['id'], '1');
expect(json['name'], 'John Doe');
expect(json['email'], '[email protected]');
});
test('copyWith should create new instance', () {
const user = User(
id: '1',
name: 'John Doe',
email: '[email protected]',
);
final updated = user.copyWith(name: 'Jane Doe');
expect(updated.id, user.id);
expect(updated.name, 'Jane Doe');
expect(updated.email, user.email);
expect(updated != user, true);
});
test('equality should work correctly', () {
const user1 = User(
id: '1',
name: 'John Doe',
email: '[email protected]',
);
const user2 = User(
id: '1',
name: 'John Doe',
email: '[email protected]',
);
expect(user1, user2);
});
});
}
Context7 es un MCP server que proporciona documentación actualizada de código para LLMs y editores de código AI. Está configurado en mcp.json y permite obtener documentación en tiempo real de las librerías de code generation.
Context7 ya está configurado en mcp.json:
{
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"],
"env": {
"CONTEXT7_API_KEY": "${CONTEXT7_API_KEY}"
}
}
}
Nota: Puedes usar Context7 sin API key (con rate limits) o obtener una API key gratuita en context7.com/dashboard.
Cuando trabajes con code generation, puedes pedirle a tu asistente de IA:
Usa context7 para obtener la documentación más reciente de freezed
Implementa json_serializable usando la documentación de context7
Consulta context7 para la mejor práctica de injectable en Flutter
Context7 MCP proporciona las siguientes herramientas:
resolve-library-id: Resuelve un nombre de librería en un ID compatible con Context7
freezed → /freezed/freezedget-library-docs: Obtiene documentación de una librería usando su ID de Context7
/freezed/freezed para documentación de Freezedtopic para enfocar la documentación (ej: "immutability", "unions")page para paginación (1-10)use context7 get-library-docs /freezed/freezed topic="immutability unions"
use context7 get-library-docs /dart-lang/build_runner topic="code generation"
use context7 get-library-docs /dart-lang/json_serializable topic="custom converters"
Las siguientes librerías están disponibles en Context7:
/freezed/freezed - Freezed para clases inmutables/dart-lang/build_runner - build_runner para generación de código/dart-lang/json_serializable - Serialización JSON/dart-lang/injectable - Dependency Injection/dart-lang/auto_route - Navigation con code generationUsa Library ID directamente: Si conoces el ID exacto, úsalo en tu prompt:
Implementa freezed usando /freezed/freezed
Especifica topics: Enfoca la documentación en temas específicos:
use context7 get-library-docs /freezed/freezed topic="copyWith unions"
Paginación: Si la documentación es extensa, usa page=2, page=3, etc.
Versión: 1.0.0 Última actualización: Diciembre 2025 Total líneas: 1,250+
development
# 🔧 Skill: Native Integration (Swift/Kotlin) ## 📋 Metadata | Atributo | Valor | |----------|-------| | **ID** | `flutter-native-integration` | | **Nivel** | 🔴 Avanzado | | **Versión** | 1.0.0 | | **Keywords** | `native-integration`, `swift`, `kotlin`, `uikit`, `android-sdk`, `native-ui` | | **Referencia** | [Flutter Platform Integration](https://docs.flutter.dev/platform-integration) | ## 🔑 Keywords para Invocación Usa cualquiera de estos keywords en tus prompts para invocar este skill:
testing
# 🎨 Skill: MVVM Pattern ## 📋 Metadata | Atributo | Valor | |----------|-------| | **ID** | `flutter-mvvm-pattern` | | **Nivel** | 🟡 Intermedio | | **Versión** | 1.0.0 | | **Keywords** | `mvvm`, `model-view-viewmodel`, `provider`, `changenotifier` | ## 🔑 Keywords para Invocación Usa cualquiera de estos keywords en tus prompts para invocar este skill: - `mvvm` - `model-view-viewmodel` - `provider` - `changenotifier` - `@skill:mvvm` ### Ejemplos de Prompts ``` Crea una app de lista de ta
development
# 🎨 Skill: Arquitectura Modular ## 📋 Metadata | Atributo | Valor | |----------|-------| | **ID** | `flutter-modular-architecture` | | **Nivel** | 🔴 Avanzado | | **Versión** | 1.0.0 | | **Keywords** | `modular`, `modular-architecture`, `module`, `multi-module` | | **Referencia** | [Flutter Modular Package](https://pub.dev/packages/flutter_modular) | ## 🔑 Keywords para Invocación Usa cualquiera de estos keywords en tus prompts para invocar este skill: - `modular` - `modular-architecture`
tools
# 📱 Skill: Mobile Testing y Debugging con Flutter MCP ## 📋 Metadata | Atributo | Valor | |----------|-------| | **ID** | `flutter-mobile-testing` | | **Nivel** | 🔴 Avanzado | | **Versión** | 1.2.0 | | **Keywords** | `mobile-testing`, `integration-test`, `flutter-mcp`, `dart-mcp`, `debugging`, `logic-analysis`, `widget-inspection`, `device-testing` | | **Referencia** | [Dart and Flutter MCP server](https://docs.flutter.dev/ai/mcp-server) \| [Mobile MCP](https://github.com/mobile-next/mobile-