Value Objects

A robust and simple implementation of the Value Object pattern for Dart and Flutter projects. This package helps you create type-safe, validated, and immutable value types for your domain models, reducing bugs and improving code clarity.

Value Objects are a fundamental concept in Domain-Driven Design (DDD). Instead of representing domain concepts with primitive types (like String for an email or double for a monetary value), you create a class that encapsulates the value, its validation rules, and its behavior.

Features

  • βœ… Type Safety: Avoid "Primitive Obsession". EmailAddress is clearer and safer than String.
  • πŸ”’ Immutability: Once created, a Value Object's value cannot be changed.
  • βš™οΈ Built-in Validation: Integrated validation logic that throws specific exceptions like InvalidValueException and RequiredValueException.
  • πŸ“ Easy Parsing: Safely parse values from strings (e.g., user input) with parse() and tryParse().
  • 🀝 Form Integration: A validator method ready to be used directly with TextFormField.
  • βš–οΈ Value Equality: Two Value Objects are equal if their underlying values are equal, not by reference.
  • πŸš€ Pre-built Implementations: A collection of common Value Objects ready to use out-of-the-box.

Getting Started

Add the package to your pubspec.yaml file:

dependencies:
value_objects: ^1.0.0 # Replace with the latest version

Then, run flutter pub get or dart pub get to install the package.

Usage

You can either use one of the pre-built Value Objects or create your own by extending the base ValueObject<T> class.

Creating a Custom Value Object

To create your own, extend ValueObject<T> and implement the doParse method, which contains your specific validation logic.

Let's create an EmailAddress Value Object:

import 'package:value_objects/value_objects.dart';

class EmailAddress extends ValueObject<String> {
EmailAddress({
required super.defaultValue,
required super.isRequired,
});

// The core parsing and validation logic for this value type.
@override
String doParse(String? parseValue) {
final email = parseValue?.trim() ?? '';
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');

// The base \`validate\` method handles the \`isRequired\` check.  
// Here, we only need to validate the format of the email itself.  
if (email.isNotEmpty && \!emailRegex.hasMatch(email)) {  
  // Throw the specific exception for clear error handling.  
  throw InvalidValueException('Invalid email format');  
}

return email;  

}
}

Using the Value Object in Flutter

Now you can use EmailAddress in your application to ensure you always have a valid email.

Instantiation and Parsing

// Create an instance (usually in your domain entity or model)
final email = EmailAddress(defaultValue: '', isRequired: true);

// The `parse` method throws a `ValueException` on failure.
try {
email.parse('test@example.com');
print('Valid email: ${email.value}'); // Output: test@example.com
} on ValueException catch (e) {
print('Validation Error: $e');
}

// The `tryParse` method returns null on failure, which is useful
// when you don't want to handle exceptions.
final parsedValue = email.tryParse('invalid-email');
if (parsedValue == null) {
print('Parsing failed!');
}

Flutter Form Validation

The .validator() method is designed to integrate directly with a TextFormField.

import 'package:flutter/material.dart';
import 'package:value_objects/value_objects.dart';

class MyForm extends StatefulWidget {
@override
_MyFormState createState() => _MyFormState();
}

class _MyFormState extends State<MyForm> {
final _formKey = GlobalKey<FormState>();
final email = EmailAddress(defaultValue: '', isRequired: true);

void _submit() {
// Validate the entire form.
final isValid = _formKey.currentState?.validate() ?? false;
if (!isValid) {
return;
}
// Save the form, which triggers the onSaved callback.
_formKey.currentState!.save();

ScaffoldMessenger.of(context).showSnackBar(  
  SnackBar(content: Text('Submitted Email: ${email.value}')),  
);  

}

@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
// Use the validator method directly!
validator: email.validator,
onSaved: (value) {
// On save, parse the validated value into the Value Object.
// This is safe because `validate` has already passed.
email.parse(value);
},
),
ElevatedButton(
onPressed: _submit,
child: Text('Submit'),
),
],
),
);
}
}

Available Value Objects

Class Description
ColorValue Represents a Color from a hex string.
CpfValue Validates a Brazilian individual taxpayer registry number (CPF).
DateTimeValue Parses and validates a DateTime from a string.
DecimalValue Handles double values, useful for monetary amounts.
EmailAddressValue Validates a standard email address format.
FullNameValue Validates that a string contains at least a first and last name.
GenericStringValue A basic String with requirement and length checks.
HtmlContentValue Intended for storing sanitized HTML content.
IntValue Parses and validates an int from a string.
MongoIdValue Validates a MongoDB ObjectId format.
PasswordValue Validates common password strength requirements.
PhoneNumberValue Validates a general phone number format.
UriValue Parses and validates a Uri from a string.

Additional Information

Filing Issues

Please file any issues, bugs, or feature requests on the official issue tracker. (<- Replace with your repo link)

Contributing

Contributions are welcome! If you'd like to contribute, please fork the repository and submit a pull request with a clear description of your changes.

License

This package is licensed under the MIT License. See the LICENSE file for more details.