Appearance
📊 Data Modeling Standards
Table of Contents
Core Standards
Model Definition Rules
- Always use
@JsonSerializableannotation for automatic JSON serialization - Use
fieldRename: FieldRename.snakefor consistent JSON field naming - Use
finalfields for immutability if needed - Follow PascalCase for class names
- Use descriptive field names with proper types
dart
@JsonSerializable(fieldRename: FieldRename.snake)
class UserModel {
final String id;
final String email;
final String? firstName;
final String? lastName;
final DateTime createdAt;
UserModel(
this.id,
this.email,
this.firstName,
this.lastName,
this.createdAt,
);
factory UserModel.fromJson(Map<String, dynamic> json) =>
_$UserModelFromJson(json);
Map<String, dynamic> toJson() => _$UserModelToJson(this);
}File Structure Requirements
- Include
partdirective for generated files - Use
.g.dartsuffix for generated files - Place models in correct directories based on scope
- Use consistent naming for model files
dart
import 'package:json_annotation/json_annotation.dart';
part 'user_model.g.dart'; // Required for code generation
@JsonSerializable(fieldRename: FieldRename.snake)
class UserModel {
// ... implementation
}Model Organization
Directory Structure Rules
- Feature-specific models go in
feature_folder/data/models/ - Global/shared models go in
lib/shared/data/models/ - Use descriptive folder names that match feature names
- Group related models in the same directory
- Use consistent file naming with
_model.dartsuffix
lib/
├── features/
│ └── auth/
│ └── data/
│ └── models/
│ ├── user_model.dart
│ ├── login_request_model.dart
│ └── auth_response_model.dart
└── shared/
└── data/
└── models/
├── api_response_model.dart
├── pagination_model.dart
└── error_model.dartModel Naming Conventions
- Use PascalCase for class names
- Use
Modelsuffix for all model classes - Use descriptive names that reflect the data structure
- Use consistent prefixes for related models (e.g.,
UserModel,UserProfileModel)
dart
// ✅ Good: Clear, descriptive names
class UserModel { }
class UserProfileModel { }
class LoginRequestModel { }
class ApiResponseModel<T> { }
// ❌ Bad: Unclear or inconsistent names
class User { }
class UserData { }
class Login { }
class Response { }Implementation Guidelines
Generic Model Rules
- Use generics for reusable model structures
- Use type constraints when appropriate
- Provide clear type parameters in documentation
- Use consistent generic naming (T, E, K, V)
dart
@JsonSerializable(fieldRename: FieldRename.snake)
class ApiResponseModel<T> {
final bool success;
final String? message;
final T? data;
final List<String>? errors;
ApiResponseModel({
required this.success,
this.message,
this.data,
this.errors,
});
factory ApiResponseModel.fromJson(
Map<String, dynamic> json,
T Function(Object? json) fromJsonT,
) => _$ApiResponseModelFromJson(json, fromJsonT);
Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
_$ApiResponseModelToJson(this, toJsonT);
}Nested Model Rules
- Use composition for complex nested structures
- Create separate models for nested objects
- Use proper serialization for nested models
- Handle null safety for optional nested models
dart
// order_model.dart
@JsonSerializable(fieldRename: FieldRename.snake)
class OrderModel {
final String id;
final UserModel customer;
final List<OrderItemModel> items;
final AddressModel? shippingAddress;
final OrderStatusModel status;
OrderModel({
required this.id,
required this.customer,
required this.items,
this.shippingAddress,
required this.status,
});
}
// order_item_model.dart
@JsonSerializable(fieldRename: FieldRename.snake)
class OrderItemModel {
final String productId;
final int quantity;
final double unitPrice;
OrderItemModel({
required this.productId,
required this.quantity,
required this.unitPrice,
});
}Code Generation Rules
Build Runner Requirements
- Always run build_runner after creating or modifying models
- Use
--delete-conflicting-outputsflag for clean builds - Check generated files into version control
bash
# Generate code for all models
flutter pub run build_runner build --delete-conflicting-outputs
# Watch mode for development
flutter pub run build_runner watch --delete-conflicting-outputsGenerated File Management
- Never edit
.g.dartfiles manually - Regenerate files when model changes
dart
// ✅ Good: Proper part directive
part 'user_model.g.dart';
// ❌ Bad: Missing part directive
// Generated files won't be includedDependencies Requirements
- Include
json_annotationin dependencies - Include
json_serializablein dev_dependencies - Include
build_runnerin dev_dependencies - Use compatible versions of all packages
Common Violations
❌ DO NOT Violate These Rules
- Don't forget
@JsonSerializableannotation - Don't skip
fieldRename: FieldRename.snakeconfiguration - Don't forget
partdirective for generated files - Don't edit
.g.dartfiles manually - Don't use inconsistent naming conventions
- Don't forget to run build_runner after changes
- Don't use
varorlateordynamicfor model fields - Don't skip null safety annotations
✅ ALWAYS Follow These Rules
- Use
@JsonSerializable(fieldRename: FieldRename.snake)for all models - Use
finalfields for immutability if needed - Use
?for nullable fields - Include
partdirective for generated files - Use PascalCase for class names
- Use camelCase for field names
- Use descriptive names for classes and fields
- Run build_runner after model changes