This package includes code generators that augment classes that are meant to be used as business entities. More concretely, the generated classes add the following features to otherwise barebones PODO - Plain Old Dart Object - classes:

  • Business rules validation
  • Extension to add copyWith method
  • Builder class

Getting Started

Validations

Start by adding the @validatableannotation to the PODO class for which you want validation code to be generated:

/// ensure the library has the part statement.
part 'recipe.g.dart';

@validatable
class Recipe {

    final String title;

    const Recipe({required this.title});
}

This will generate a Validator class that will contain a validation method for each of the properties of the PODO class. By default each validation method will return null, as examplified:

/// This is an example of a generated validator class.
class RecipeValidator implements Validator {
  RecipeValidator.create();

  static final RecipeValidator _singleton = RecipeValidator.create();
  factory RecipeValidator() => _singleton;

  ValidationError? validateTitle(String value) {
    return null;
  }

  @override
  ErrorList validate(covariant Recipe entity) {
    var errors = <ValidationError>[];
    ValidationError? error;
    if ((error = validateTitle(entity.title)) != null) {
      errors.add(error!);
    }

    return ErrorList(errors);
  }

  @override
  void validateThrowing(covariant Recipe entity) {
    var errors = validate(entity);
    if (errors.validationErrors.isNotEmpty) throw errors;
  }

Annotate each field with a rule that you want to apply to that field:

/// ensure the library has the part statement.
part 'ingredient.g.dart';

@validatable
class Ingredient {

  @StringLength(minLength: 10)
  final String description;

  @StringLength(maxLength: 10)
  final String? notes;

  @StringLength(minLength: 2)
  final String? tag;

  @DoubleRange(minValue: 10, maxValue: 20)
  final double quantity;

  @Range(minValue: 10)
  final Decimal precision;

  @Range(minValue: 10, maxValue: 20)
  final int intQuantity;

  @Range(minValue: 10, maxValue: 20)
  final int? nintQuantity;

  @Range(minValue: 10, maxValue: 20)
  @required
  final int? rInt;

  Ingredient({
    required this.description,
    required this.quantity,
    required this.precision,
    required this.intQuantity,
    this.notes,
    this.tag,
    this.nintQuantity,
    this.rInt,
  });
}

Builder

Add a @builder annotation to the PODO:

/// ensure the library has the part statement.
part 'recipe.g.dart';

@builder
class Recipe {
  final String title;

  final String? description;
  
  Recipe({
    required this.title,
    this.description,
  });
}

This will generate a non-immutable builder class:

class RecipeBuilder implements Builder<Recipe> {
  String title;
  String? description;

  RecipeBuilder({
    required this.title,
    this.description,
  });

  factory RecipeBuilder.fromRecipe(Recipe entity) {
    return RecipeBuilder(
      title: entity.title,
      description: entity.description,
    );
  }

  @override
  Recipe build() {
    var entity = Recipe(
      title: title,
      description: description,
    );
    RecipeValidator().validateThrowing(entity);
    return entity;
  }
}

copyWith

Add a @builder annotation to the PODO:

/// ensure the library has the part statement.
part 'recipe.g.dart';

@copyWith
class Recipe {
  final String title;

  final String? description;
  
  Recipe({
    required this.title,
    this.description,
  });
}

This will generate an extension to the PODO that adds the copyWith method:

extension RecipeCopyWithExtension on Recipe {
  Recipe copyWith({
    String? title,
    String? description,
    bool setDescriptionToNull = false,
  }) {
    return Recipe(
      title: title ?? this.title,
      description:
          setDescriptionToNull ? null : description ?? this.description,
    );
  }
}

Context

This package is part of a set of losely integrated packages that constitute the SquareAlfa Dart Framework.