Entityfy
A powerful code generator for Dart that automatically creates Entity and UI Model classes with conversion methods using simple annotations. This package streamlines the process of implementing Clean Architecture patterns by generating complete classes and mappers for seamless data transformation between layers.
Features
- π Complete Class Generation: Generate Entity and UI Model classes with a single annotation
- π Bidirectional Mapping: Automatic
toEntity()
andtoUiModel()
conversion methods - ποΈ Clean Architecture Ready: Perfect for implementing domain-driven design patterns
- π Type Safety: Full type checking and validation during code generation
- π οΈ Build Runner Integration: Seamless integration with Dart's build system
- π― Zero Runtime Dependencies: Generated code has no external dependencies
- π Nested Model Support: Automatically handles complex nested structures with recursive conversion
- βοΈ Flexible Configuration: Choose what to generate with boolean flags (
generateEntity
,generateUiModel
,generateFakeList
) - π§ͺ Fake Data Generation: Static
fakeList()
methods for creating realistic test data (v2.1.0+) - π CopyWith Methods: Immutable update methods for all generated classes (v2.1.0+)
Getting Started
Prerequisites
- Dart SDK ^3.8.0
build_runner
package for code generation
Installation
Add the package to your pubspec.yaml
:
dependencies:
entityfy: ^2.1.0
dev_dependencies:
entityfy_generator: ^2.1.0
build_runner: ^2.4.15
Usage
Complete Class Generation Example
With Entityfy 2.0, you can generate complete classes and mappers with a single annotation:
import 'package:entityfy/entityfy.dart';
// Model class that generates both Entity and UI Model classes
@Entityfy(generateEntity: true, generateUiModel: true)
class UserModel {
final String id;
final String name;
final String email;
final AddressModel address;
final List<String> tags;
const UserModel({
required this.id,
required this.name,
required this.email,
required this.address,
required this.tags,
});
}
// Nested model class
@Entityfy(generateEntity: true, generateUiModel: true)
class AddressModel {
final String street;
final String city;
final String country;
final String zipCode;
const AddressModel({
required this.street,
required this.city,
required this.country,
required this.zipCode,
});
}
// Don't forget to include the generated part
part 'user_model.entityfy.g.dart';
Entity-Only Generation
If you only need entity classes and mappers:
@Entityfy(generateEntity: true)
class ProductModel {
final String id;
final String name;
final double price;
final DateTime createdAt;
final List<CategoryModel> categories;
const ProductModel({
required this.id,
required this.name,
required this.price,
required this.createdAt,
required this.categories,
});
}
@Entityfy(generateEntity: true)
class CategoryModel {
final String id;
final String name;
const CategoryModel({
required this.id,
required this.name,
});
}
part 'product_model.entityfy.g.dart';
UI Model-Only Generation
For generating UI models from existing entity classes:
@Entityfy(generateUiModel: true)
class UserEntity {
final String id;
final String name;
final String email;
final bool isActive;
const UserEntity({
required this.id,
required this.name,
required this.email,
required this.isActive,
});
}
part 'user_entity.entityfy.g.dart';
Generated Code
After running the code generator, you'll get a .entityfy.g.dart
file with complete classes and mappers:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user_model.dart';
// Generated Entity Class
class UserEntity {
final String id;
final String name;
final String email;
final AddressEntity address;
final List<String> tags;
const UserEntity({
required this.id,
required this.name,
required this.email,
required this.address,
required this.tags,
});
factory UserEntity.fromJson(Map<String, dynamic> json) {
return UserEntity(
id: json['id'] as String? ?? '',
name: json['name'] as String? ?? '',
email: json['email'] as String? ?? '',
address: AddressEntity.fromJson(json['address']),
tags: (json['tags'] as List<dynamic>?)?.cast<String>() ?? [],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
'address': address.toJson(),
'tags': tags,
};
}
}
// Generated UI Model Class
class UserUiModel {
final String id;
final String name;
final String email;
final AddressUiModel address;
final List<String> tags;
const UserUiModel({
required this.id,
required this.name,
required this.email,
required this.address,
required this.tags,
});
// ... similar fromJson/toJson methods
}
// Generated Entity Mapper Extension
extension UserModelEntityMapper on UserModel {
UserEntity toEntity() {
return UserEntity(
id: id,
name: name,
email: email,
address: address.toEntity(),
tags: tags,
);
}
}
// Generated UI Model Mapper Extension
extension UserEntityUiModelMapper on UserEntity {
UserUiModel toUiModel() {
return UserUiModel(
id: id,
name: name,
email: email,
address: address.toUiModel(),
tags: tags,
);
}
}
Running the Generator
Run the code generator using build_runner:
# Generate code once
dart run build_runner build
NEW: Fake Data & CopyWith (v2.1.0+)
Fake Data Generation
Generate realistic test data for development and testing:
@Entityfy(generateEntity: true, generateFakeList: true)
class ProductModel {
final String id;
final String name;
final double price;
final bool isAvailable;
final DateTime createdAt;
final List<String> tags;
const ProductModel({
required this.id,
required this.name,
required this.price,
required this.isAvailable,
required this.createdAt,
required this.tags,
});
}
part 'product_model.entityfy.g.dart';
// Usage:
final fakeProducts = ProductEntity.fakeList(count: 50);
// Generates 50 realistic ProductEntity instances with mock data
CopyWith Methods
All generated classes include copyWith()
methods for immutable updates:
@Entityfy(generateEntity: true, generateUiModel: true)
class UserModel {
final String id;
final String name;
final String email;
final bool isActive;
const UserModel({
required this.id,
required this.name,
required this.email,
required this.isActive,
});
}
// Usage:
final originalEntity = userModel.toEntity();
final updatedEntity = originalEntity.copyWith(
name: 'New Name',
isActive: false,
);
final updatedUiModel = originalEntity.toUiModel().copyWith(
email: 'newemail@example.com',
);
Configuration Combinations
Mix and match generation options:
// Entity + Fake Data only
@Entityfy(generateEntity: true, generateFakeList: true)
class TestModel { /* ... */ }
// All features combined
@Entityfy(
generateEntity: true,
generateUiModel: true,
generateFakeList: true,
)
class CompleteModel { /* ... */ }
// Only fake data (for existing entities)
@Entityfy(generateFakeList: true)
class MockDataModel { /* ... */ }
How It Works
- Annotation Processing: The generator scans your code for classes annotated with
@Entityfy
- Configuration Analysis: Reads boolean flags (
generateEntity
,generateUiModel
,generateFakeList
) to determine what to generate - Class Generation: Creates complete Entity and/or UI Model classes with constructors,
fromJson()
,toJson()
, andcopyWith()
methods - Type Analysis: Analyzes the source classes to understand their structure and relationships
- Mapper Generation: Creates extension methods with
toEntity()
andtoUiModel()
functions - Fake Data Generation: Generates static
fakeList()
methods with realistic mock data whengenerateFakeList: true
- Nested Handling: Automatically detects nested models and applies recursive conversion
- Combined Output: Generates all code in a single
.entityfy.g.dart
file per source file
Best Practices
- Consistent Naming: Use clear, descriptive names for your models (e.g.,
UserModel
,ProductModel
) - Annotation Strategy: Choose appropriate flags based on your architecture needs
- Type Matching: Ensure compatible types between generated classes and existing code
- Part Files: Always include the generated
.entityfy.g.dart
part file in your source classes - Clean Builds: Use
dart run build_runner clean
when encountering generation issues - Nested Models: Annotate all nested models that need conversion for automatic recursive mapping
- DateTime Handling: The generator automatically handles DateTime serialization with ISO8601 format
- Fake Data Usage: Use
generateFakeList: true
for models that need test data generation (v2.1.0+) - CopyWith Pattern: Leverage generated
copyWith()
methods for immutable updates in your domain logic (v2.1.0+) - Testing Strategy: Combine fake data generation with copyWith methods for comprehensive testing scenarios (v2.1.0+)
Clean Architecture Integration
This package is highly recommended when implementing Clean Architecture patterns in your Dart/Flutter applications. Here's why and how to use it effectively:
Why Use Entityfy in Clean Architecture?
- ποΈ Boundary Crossing: Automates data transformation when crossing architectural layers
- π Data β Domain Conversion: Seamlessly converts data models (JSON, DB) to domain entities
- π Dependency Rule Compliance: Keeps domain entities pure while adapting external models
- β‘ Reduces Boilerplate: Eliminates manual mapper implementations and reduces human error
Ideal Use Cases
- Multiple Data Sources: When working with REST APIs, GraphQL, and local databases
- Complex Business Logic: Applications requiring well-defined domain entities
- Team Consistency: Ensures uniform conversion patterns across the codebase
- Type Safety Priority: Projects that prioritize compile-time safety over runtime mapping
Architecture Flow Example
// Data Layer (External)
@Entityfy(generateEntity: true)
class UserModel {
final String id;
final String email;
final AddressModel address;
const UserModel({
required this.id,
required this.email,
required this.address,
});
// JSON serialization logic would be here
factory UserModel.fromJson(Map<String, dynamic> json) => UserModel(
id: json['id'],
email: json['email'],
address: AddressModel.fromJson(json['address']),
);
}
@Entityfy(generateEntity: true)
class AddressModel {
final String street;
final String city;
const AddressModel({
required this.street,
required this.city,
});
factory AddressModel.fromJson(Map<String, dynamic> json) => AddressModel(
street: json['street'],
city: json['city'],
);
}
// Generated classes will be:
// - UserEntity (pure domain entity)
// - AddressEntity (pure domain entity)
// - UserModel.toEntity() extension method
// - AddressModel.toEntity() extension method
// Usage in Repository (Data β Domain)
class UserRepository {
Future<UserEntity> getUser(String id) async {
final userModel = await apiClient.fetchUser(id);
return userModel.toEntity(); // Generated method!
}
}
Key Concepts to Understand
Before implementing, ensure your team understands:
- Entities vs Models: Domain entities represent business concepts, models represent data structure
- Mappers/Adapters Pattern: How adapters facilitate communication between layers
- Build-time vs Runtime: Why code generation provides better performance and type safety
Limitations
- At least one of
generateEntity
orgenerateUiModel
must be set totrue
in the annotation - Generated classes use unnamed constructors with named parameters
- Complex generic types beyond
List<T>
may require manual handling - Nested models must also be annotated with
@Entityfy
for automatic conversion - Generated file extension is always
.entityfy.g.dart
Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
License
This project is licensed under the MIT License - see the LICENSE file for details.
References
Code Generation
Clean Architecture & Design Patterns
Libraries
- entityfy
- A powerful code generation library for automatically creating Entity and UI Model classes with mapping methods.