Lesson 55 minNEW

Project 4 — Production Dart Package

00:00 / 00:00

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:

ValidatorWhat 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.yaml authoring
  • ✅ Public API design
  • /// documentation comments
  • ✅ Unit testing with the test package
  • 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

bash
dart 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:

yaml
name: 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 changes
  • MINOR (1.1.0) — new features, backwards compatible
  • MAJOR (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:

bash
dart 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

dart
import '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 .gitignore is correct)
  • Any missing pubspec fields
  • pub.dev score analysis

When ready to publish for real:

bash
dart 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:

CategoryWhat It Checks
LikesCommunity upvotes
Pub PointsREADME, CHANGELOG, docs, license, tests
PopularityDownload metrics

Tips for maximum pub points:

  • ✅ Add /// doc comments to every public API member
  • ✅ Include a LICENSE file (MIT is most common)
  • ✅ Have a meaningful CHANGELOG.md
  • ✅ Pass dart analyze with zero warnings
  • ✅ Have high test coverage

💡 Key Takeaways

ConceptWhere Used
dart create --template=packageScaffolding
Barrel exports (export 'src/...')Public API
/// doc commentsAPI documentation
RegExpEmail, URL, phone patterns
Luhn algorithmCredit card validation
test packageUnit testing
group() / test()Test organization
pubspec.yaml topicspub.dev discoverability
dart pub publish --dry-runPre-flight check

Summary

You've just completed the full lifecycle of a Dart package:

  1. 📦 Scaffolded a proper package structure
  2. 🔧 Built a useful, real-world validation library
  3. 🧪 Tested every feature with unit tests
  4. 📝 Documented the public API with doc comments
  5. 🚀 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!

WhatsApp