Project 4 — Production Dart Package
Project 4 — Production Dart Package
In this lesson you'll go from writing Dart code to publishing it for the world to use. We'll build dart_validator — a reusable input validation package — and walk through every step required to publish it on pub.dev, Dart's official package registry.
🎯 What We're Building
A Dart package called dart_validator that provides:
| Validator | What It Checks |
|---|---|
isEmail(String) | Valid email address |
isUrl(String) | Valid URL (http/https) |
isPhoneNumber(String) | 10-15 digit phone number |
isStrongPassword(String) | Length, uppercase, digit, special char |
isCreditCard(String) | Luhn algorithm check |
isNumeric(String) | Only digits |
isAlpha(String) | Only letters |
isBetween(num, min, max) | Number in range |
🧠 Concepts Covered
- ✅ Package project structure
- ✅
pubspec.yamlauthoring - ✅ Public API design
- ✅
///documentation comments - ✅ Unit testing with the
testpackage - ✅
dart pub publish --dry-run - ✅ Semantic versioning
- ✅ README writing for pub.dev
📁 Package Structure
dart_validator/
├── lib/
│ ├── dart_validator.dart ← Public export barrel
│ └── src/
│ ├── email_validator.dart
│ ├── url_validator.dart
│ ├── phone_validator.dart
│ ├── password_validator.dart
│ └── common_validators.dart
├── test/
│ └── dart_validator_test.dart ← Unit tests
├── example/
│ └── main.dart ← Usage example
├── pubspec.yaml
├── README.md
├── CHANGELOG.md
└── LICENSE
Step 1 — Create the Package
bashdart create --template=package dart_validator cd dart_validator
The --template=package flag scaffolds the correct structure. Open pubspec.yaml — it will look like this by default.
Step 2 — Configure pubspec.yaml
Replace the contents of pubspec.yaml:
yamlname: dart_validator description: > A comprehensive, zero-dependency input validation library for Dart. Validate emails, URLs, phone numbers, passwords, and more. version: 1.0.0 homepage: https://github.com/yourusername/dart_validator repository: https://github.com/yourusername/dart_validator issue_tracker: https://github.com/yourusername/dart_validator/issues topics: - validation - utilities - forms environment: sdk: '>=3.0.0 <4.0.0' dev_dependencies: test: ^1.24.0 lints: ^3.0.0
💡 Semantic Versioning: Use MAJOR.MINOR.PATCH:
PATCH(1.0.1) — bug fixes, no breaking changesMINOR(1.1.0) — new features, backwards compatibleMAJOR(2.0.0) — breaking changes to public API
Step 3 — Individual Validator Files
lib/src/email_validator.dart
dart// lib/src/email_validator.dart /// Validates email address strings. class EmailValidator { // Regex pattern for RFC 5322 simplified email validation static final _emailRegex = RegExp( r'^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$', ); /// Returns `true` if [email] is a valid email address. /// /// Example: /// ```dart /// EmailValidator.isValid('user@example.com'); // true /// EmailValidator.isValid('not-an-email'); // false /// ``` static bool isValid(String email) { if (email.trim().isEmpty) return false; return _emailRegex.hasMatch(email.trim()); } /// Returns an error message if invalid, or `null` if valid. /// Useful for form validation. static String? validate(String email) { if (email.trim().isEmpty) return 'Email is required.'; if (!isValid(email)) return 'Please enter a valid email address.'; return null; } }
lib/src/url_validator.dart
dart// lib/src/url_validator.dart /// Validates URL strings. class UrlValidator { static final _urlRegex = RegExp( r'^https?:\/\/(www\.)?' r'[-a-zA-Z0-9@:%._\+~#=]{1,256}' r'\.[a-zA-Z0-9()]{1,6}' r'\b([-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$', ); /// Returns `true` if [url] is a valid HTTP or HTTPS URL. /// /// Example: /// ```dart /// UrlValidator.isValid('https://flutter.dev'); // true /// UrlValidator.isValid('ftp://example.com'); // false /// ``` static bool isValid(String url) { if (url.trim().isEmpty) return false; return _urlRegex.hasMatch(url.trim()); } /// Returns an error message if invalid, or `null` if valid. static String? validate(String url) { if (url.trim().isEmpty) return 'URL is required.'; if (!isValid(url)) return 'Please enter a valid URL (must start with http:// or https://).'; return null; } }
lib/src/password_validator.dart
dart// lib/src/password_validator.dart /// Password strength validation result. class PasswordStrength { final bool isStrong; final List<String> missingRequirements; const PasswordStrength({ required this.isStrong, required this.missingRequirements, }); } /// Validates password strength. class PasswordValidator { final int minLength; final bool requireUppercase; final bool requireDigit; final bool requireSpecialChar; /// Creates a [PasswordValidator] with configurable rules. /// /// Defaults require: length >= 8, uppercase letter, digit, special character. const PasswordValidator({ this.minLength = 8, this.requireUppercase = true, this.requireDigit = true, this.requireSpecialChar = true, }); /// Returns a [PasswordStrength] object with validation details. /// /// Example: /// ```dart /// final validator = PasswordValidator(); /// final result = validator.check('Hello@123'); /// print(result.isStrong); // true /// ``` PasswordStrength check(String password) { final missing = <String>[]; if (password.length < minLength) { missing.add('At least $minLength characters'); } if (requireUppercase && !password.contains(RegExp(r'[A-Z]'))) { missing.add('At least one uppercase letter'); } if (requireDigit && !password.contains(RegExp(r'[0-9]'))) { missing.add('At least one digit'); } if (requireSpecialChar && !password.contains(RegExp(r'[!@#\$%^&*(),.?":{}|<>]'))) { missing.add('At least one special character (!@#\$ etc.)'); } return PasswordStrength( isStrong: missing.isEmpty, missingRequirements: missing, ); } /// Simple boolean check with default rules. bool isStrong(String password) => check(password).isStrong; }
lib/src/common_validators.dart
dart// lib/src/common_validators.dart /// A collection of common, general-purpose validators. class Validator { /// Returns `true` if [value] contains only digits. static bool isNumeric(String value) => value.isNotEmpty && RegExp(r'^\d+$').hasMatch(value); /// Returns `true` if [value] contains only alphabetic characters. static bool isAlpha(String value) => value.isNotEmpty && RegExp(r'^[a-zA-Z]+$').hasMatch(value); /// Returns `true` if [value] is a phone number (10-15 digits, optional +). static bool isPhoneNumber(String value) { final stripped = value.replaceAll(RegExp(r'[\s\-\(\)]'), ''); return RegExp(r'^\+?\d{10,15}$').hasMatch(stripped); } /// Returns `true` if [number] is between [min] and [max] (inclusive). static bool isBetween(num number, num min, num max) => number >= min && number <= max; /// Validates a credit card number using the Luhn algorithm. /// /// Example: /// ```dart /// Validator.isCreditCard('4532015112830366'); // true (valid Visa test number) /// ``` static bool isCreditCard(String value) { final digits = value.replaceAll(RegExp(r'\D'), ''); if (digits.length < 13 || digits.length > 19) return false; int sum = 0; bool alternate = false; for (int i = digits.length - 1; i >= 0; i--) { int n = int.parse(digits[i]); if (alternate) { n *= 2; if (n > 9) n -= 9; } sum += n; alternate = !alternate; } return sum % 10 == 0; } Validator._(); // Prevent instantiation — utility class }
Step 4 — The Public Barrel File
Create lib/dart_validator.dart — this is what users import:
dart// lib/dart_validator.dart /// dart_validator — A comprehensive input validation library. /// /// ## Usage /// ```dart /// import 'package:dart_validator/dart_validator.dart'; /// /// // Email /// bool ok = EmailValidator.isValid('user@example.com'); /// /// // Password /// final result = PasswordValidator().check('Hello@123'); /// print(result.isStrong); // true /// /// // Common /// bool isNum = Validator.isNumeric('12345'); /// ``` library dart_validator; export 'src/email_validator.dart'; export 'src/url_validator.dart'; export 'src/password_validator.dart'; export 'src/common_validators.dart';
💡 Barrel files collect all exports in one place. Users write import 'package:dart_validator/dart_validator.dart' and get everything — clean and simple.
Step 5 — Write Unit Tests
Create test/dart_validator_test.dart:
dart// test/dart_validator_test.dart import 'package:dart_validator/dart_validator.dart'; import 'package:test/test.dart'; void main() { // ─── EMAIL TESTS ──────────────────────────────────────────── group('EmailValidator', () { test('accepts valid email addresses', () { expect(EmailValidator.isValid('user@example.com'), isTrue); expect(EmailValidator.isValid('name+tag@domain.co.uk'), isTrue); expect(EmailValidator.isValid('USER@DOMAIN.COM'), isTrue); }); test('rejects invalid email addresses', () { expect(EmailValidator.isValid('not-an-email'), isFalse); expect(EmailValidator.isValid('missing@tld'), isFalse); expect(EmailValidator.isValid('@nodomain.com'), isFalse); expect(EmailValidator.isValid(''), isFalse); }); test('validate() returns null for valid emails', () { expect(EmailValidator.validate('user@example.com'), isNull); }); test('validate() returns error string for invalid emails', () { expect(EmailValidator.validate('bad'), isNotNull); expect(EmailValidator.validate(''), isNotNull); }); }); // ─── URL TESTS ─────────────────────────────────────────────── group('UrlValidator', () { test('accepts valid URLs', () { expect(UrlValidator.isValid('https://flutter.dev'), isTrue); expect(UrlValidator.isValid('http://example.com/path?q=1'), isTrue); }); test('rejects invalid URLs', () { expect(UrlValidator.isValid('not-a-url'), isFalse); expect(UrlValidator.isValid('ftp://file.com'), isFalse); expect(UrlValidator.isValid(''), isFalse); }); }); // ─── PASSWORD TESTS ────────────────────────────────────────── group('PasswordValidator', () { final validator = PasswordValidator(); test('accepts strong passwords', () { expect(validator.isStrong('Hello@123'), isTrue); expect(validator.isStrong('S3cur3P@ssword!'), isTrue); }); test('rejects weak passwords', () { expect(validator.isStrong('short'), isFalse); // Too short expect(validator.isStrong('alllowercase1!'), isFalse); // No uppercase expect(validator.isStrong('NoDigits!'), isFalse); // No digit }); test('lists missing requirements', () { final result = validator.check('weak'); expect(result.isStrong, isFalse); expect(result.missingRequirements, isNotEmpty); }); test('respects custom configuration', () { final lenient = PasswordValidator( minLength: 4, requireUppercase: false, requireSpecialChar: false, ); expect(lenient.isStrong('pass1'), isTrue); }); }); // ─── COMMON VALIDATOR TESTS ────────────────────────────────── group('Validator', () { test('isNumeric', () { expect(Validator.isNumeric('12345'), isTrue); expect(Validator.isNumeric('123abc'), isFalse); expect(Validator.isNumeric(''), isFalse); }); test('isAlpha', () { expect(Validator.isAlpha('HelloWorld'), isTrue); expect(Validator.isAlpha('Hello123'), isFalse); }); test('isPhoneNumber', () { expect(Validator.isPhoneNumber('+1 (555) 123-4567'), isTrue); expect(Validator.isPhoneNumber('9876543210'), isTrue); expect(Validator.isPhoneNumber('123'), isFalse); }); test('isBetween', () { expect(Validator.isBetween(5, 1, 10), isTrue); expect(Validator.isBetween(0, 1, 10), isFalse); }); test('isCreditCard validates Luhn algorithm', () { expect(Validator.isCreditCard('4532015112830366'), isTrue); // Valid expect(Validator.isCreditCard('1234567890123456'), isFalse); // Invalid }); }); }
Run all tests:
bashdart test
You should see:
00:01 +16: All tests passed!
Step 6 — Write the Example
Create example/main.dart:
dart// example/main.dart import 'package:dart_validator/dart_validator.dart'; void main() { // Email validation print('=== Email Validation ==='); print(EmailValidator.isValid('hello@world.com')); // true print(EmailValidator.validate('bad-email')); // Error string // URL validation print('\n=== URL Validation ==='); print(UrlValidator.isValid('https://dart.dev')); // true // Password strength print('\n=== Password Strength ==='); final result = PasswordValidator().check('Weak'); print('Strong: ${result.isStrong}'); print('Missing: ${result.missingRequirements}'); // Common validators print('\n=== Common Validators ==='); print(Validator.isNumeric('42')); // true print(Validator.isAlpha('DartLang')); // true print(Validator.isPhoneNumber('+919876543210')); // true print(Validator.isCreditCard('4532015112830366')); // true }
Step 7 — Write the README
Create README.md:
markdown# dart_validator A comprehensive, zero-dependency input validation library for Dart and Flutter. ## Features - ✅ Email validation - ✅ URL validation (HTTP/HTTPS) - ✅ Phone number validation - ✅ Password strength checker - ✅ Credit card validation (Luhn) - ✅ Numeric, alpha checks ## Installation ```yaml dependencies: dart_validator: ^1.0.0
Usage
dartimport 'package:dart_validator/dart_validator.dart'; // Email EmailValidator.isValid('user@example.com'); // true // Password final validator = PasswordValidator(minLength: 10); final result = validator.check('MySecure@Pass1'); print(result.isStrong); // true print(result.missingRequirements); // [] // Credit card Validator.isCreditCard('4532015112830366'); // true
API Reference
See the API docs on pub.dev.
---
## Step 8 — Publish Checklist & Dry Run
Before publishing, run the analysis and dry-run:
```bash
# Check for any code issues
dart analyze
# Run tests one final time
dart test
# Simulate publishing — shows what will be uploaded and any warnings
dart pub publish --dry-run
The dry-run output tells you:
- Which files will be included (check
.gitignoreis correct) - Any missing pubspec fields
- pub.dev score analysis
When ready to publish for real:
bashdart pub publish
You'll be prompted to log into your Google account (pub.dev uses Google OAuth).
📊 pub.dev Score System
pub.dev automatically scores packages in three areas:
| Category | What It Checks |
|---|---|
| Likes | Community upvotes |
| Pub Points | README, CHANGELOG, docs, license, tests |
| Popularity | Download metrics |
Tips for maximum pub points:
- ✅ Add
///doc comments to every public API member - ✅ Include a
LICENSEfile (MIT is most common) - ✅ Have a meaningful
CHANGELOG.md - ✅ Pass
dart analyzewith zero warnings - ✅ Have high test coverage
💡 Key Takeaways
| Concept | Where Used |
|---|---|
dart create --template=package | Scaffolding |
Barrel exports (export 'src/...') | Public API |
/// doc comments | API documentation |
RegExp | Email, URL, phone patterns |
| Luhn algorithm | Credit card validation |
test package | Unit testing |
group() / test() | Test organization |
pubspec.yaml topics | pub.dev discoverability |
dart pub publish --dry-run | Pre-flight check |
Summary
You've just completed the full lifecycle of a Dart package:
- 📦 Scaffolded a proper package structure
- 🔧 Built a useful, real-world validation library
- 🧪 Tested every feature with unit tests
- 📝 Documented the public API with doc comments
- 🚀 Published to pub.dev
This package is now ready for the world to use. Contributing to the Dart ecosystem is one of the best ways to grow as a developer and build your public portfolio!