dart_suite 0.0.6
dart_suite: ^0.0.6 copied to clipboard
A set of utility libraries for Dart that makes using many Dart libraries easier and more convenient, or adds additional functionality.
๐ฆ Other Packages in This Suite
๐งฉ bloc_suite โ Lightweight state-management helpers and reusable BLoC utilities for predictable app architecture.
โ๏ธ gen_suite โ Code-generation tools and annotations to automate boilerplate and speed up development.
๐ Explore the full suite โ
Dart Suite #
A versatile Dart package offering a comprehensive collection of utilities, extensions, and data structures for enhanced development productivity.
๐ Table of Contents #
- ๐ Quick Start
- ๐ Asynchronous Utilities
- ๐พ Data Structures & Algorithms
- ๐ Utility Typedefs
- โฐ Time Utilities
- ๐ค Text Processing
- ๐ URL Schemes
- ๐๏ธ Annotations & Code Generation
- ๐ง Validation
- ๐ Extensions
- ๐ค Contributing
- ๐ License
๐ Quick Start #
Add dart_suite to your pubspec.yaml:
dependencies:
dart_suite: ^latest_version
Then import the package:
import 'package:dart_suite/dart_suite.dart';
๐ Asynchronous Utilities #
๐ Retry & RetryPolicy #
Smart retry mechanisms with exponential backoff and customizable policies
RetryPolicy provides a flexible way to configure retry strategies for async operations. You can set max attempts, delays, backoff, and which exceptions to retry.
๐ฏ Key Features
- โ Centralized retry logic for consistency across your app
- โ๏ธ Exponential backoff with customizable multipliers
- ๐๏ธ Pre-configured policies (default, aggressive, no-retry)
- ๐ Custom error filtering with retryable exceptions
- ๐ Detailed logging and error callbacks
๐ Usage Examples
import 'package:dart_suite/dart_suite.dart';
// Create a custom retry policy
final policy = RetryPolicy(
maxAttempts: 5,
initialDelay: Duration(milliseconds: 500),
maxDelay: Duration(seconds: 10),
backoffMultiplier: 2.0,
retryableExceptions: [TimeoutException, SocketException],
);
// Method 1: Using retryWithPolicy function
final result = await retryWithPolicy(
() async => await fetchData(),
policy: policy,
retryIf: (e) => e is TimeoutException,
onRetry: (e, attempt) => print('Retry $attempt after error: $e'),
);
// Method 2: Using extension method
final value = await (() async => await fetchData())
.executeWithPolicy(policy: policy);
// Method 3: Using Retry class
final retry = Retry(policy: policy);
final data = await retry.execute(() async => await fetchData());
๐๏ธ Pre-configured Policies
| Policy | Attempts | Initial Delay | Backoff | Use Case |
|---|---|---|---|---|
RetryPolicy.defaultPolicy |
3 | 1s | 2x | General purpose |
RetryPolicy.aggressivePolicy |
5 | 0.5s | 1.5x | Critical operations |
RetryPolicy.noRetry |
1 | - | - | Testing/debugging |
// Quick setup with pre-configured policies
final result = await retryWithPolicy(
() async => await criticalApiCall(),
policy: RetryPolicy.aggressivePolicy,
);
โฑ๏ธ Debounce #
Delay function execution until after a specified wait time has elapsed since the last invocation
Debounce is perfect for scenarios like search inputs, API calls, or any operation that should only execute after user activity has stopped.
๐ฏ Key Features
- โณ Configurable delay with trailing/leading edge execution
- ๐ Automatic cancellation of previous pending executions
- ๐ Max wait limits to prevent indefinite delays
- ๐๏ธ Flexible execution modes (leading, trailing, or both)
๐ Usage Examples
import 'package:dart_suite/dart_suite.dart';
// Example 1: Search API debouncing
void searchApi(String query) async {
final results = await api.search(query);
updateUI(results);
}
final debouncedSearch = Debounce(
searchApi,
const Duration(milliseconds: 300),
);
// In your UI/input handler
void onSearchInput(String query) {
debouncedSearch([query]); // Will only execute after 300ms of inactivity
}
// Example 2: Leading edge execution (immediate first call)
final debouncedButton = Debounce(
handleButtonPress,
const Duration(milliseconds: 1000),
leading: true, // Execute immediately on first call
trailing: false, // Don't execute after delay
);
// Example 3: Using extension method with max wait
final debouncedSave = autoSave.debounced(
const Duration(milliseconds: 500),
maxWait: const Duration(seconds: 2), // Force execution after 2s max
);
// Example 4: Advanced configuration
final debouncer = Debounce(
expensiveOperation,
const Duration(milliseconds: 250),
leading: false,
trailing: true,
maxWait: const Duration(seconds: 1),
);
๐ก Use Cases
| Scenario | Configuration | Benefit |
|---|---|---|
| Search Input | 300ms delay, trailing | Reduces API calls |
| Auto-save | 500ms delay + 2s max wait | Balances UX and data safety |
| Button Clicks | 1s delay, leading only | Prevents accidental double-clicks |
| Resize Events | 100ms delay, trailing | Optimizes performance |
๐ก๏ธ Guard #
Safe exception handling with default values and optional error callbacks
Guard provides a clean way to handle exceptions in both synchronous and asynchronous operations without verbose try-catch blocks.
๐ฏ Key Features
- ๐ก๏ธ Exception safety with graceful fallbacks
- ๐ Sync & async support with unified API
- ๐ Custom error handling with optional callbacks
- ๐๏ธ Flexible error behavior (suppress, log, or rethrow)
๐ Usage Examples
import 'package:dart_suite/dart_suite.dart';
// Example 1: Basic guard with default value
final result = guard(
() => int.parse('invalid_number'),
def: -1,
onError: (e) => print('Parsing error: $e'),
); // Returns -1 instead of throwing
// Example 2: Async guard with API fallback
final userData = await asyncGuard(
() => fetchUserFromApi(userId),
def: User.guest(), // Fallback user
onError: (e) => logError('API failed', e),
);
// Example 3: Safe file operations
final success = guardSafe(
() => File('temp.txt').deleteSync(),
onError: (e) => print('Could not delete file: $e'),
); // Returns true if successful, false if error
// Example 4: Using extension methods
final config = (() => loadConfigFromFile())
.guard(def: Config.defaultConfig());
final apiData = (() async => await fetchCriticalData())
.asyncGuard(
def: [],
reThrow: false, // Don't rethrow exceptions
);
// Example 5: Multiple fallback strategies
final imageUrl = guard(
() => user.profileImage?.url,
def: guard(
() => getDefaultAvatarUrl(user.gender),
def: 'assets/default_avatar.png',
),
);
๐ก Use Cases
| Scenario | Guard Type | Benefit |
|---|---|---|
| API Calls | asyncGuard |
Graceful degradation |
| File I/O | guardSafe |
Prevent crashes |
| Parsing | guard |
Default values |
| Configuration | guard |
Fallback configs |
๐ฆ Throttle #
Rate limiting for function calls to prevent excessive executions
Throttle ensures a function is called at most once per specified time interval, perfect for performance optimization and rate limiting.
๐ฏ Key Features
- โฑ๏ธ Configurable intervals with precise timing control
- ๐๏ธ Leading/trailing execution modes
- ๐ Automatic scheduling of delayed executions
- ๐ Performance optimization for expensive operations
๐ Usage Examples
import 'package:dart_suite/dart_suite.dart';
// Example 1: API rate limiting
void updateServer(Map<String, dynamic> data) async {
await api.updateUserProfile(data);
}
final throttledUpdate = Throttle(
updateServer,
const Duration(seconds: 1), // Max 1 call per second
);
// Multiple rapid calls will be throttled
throttledUpdate([profileData1]);
throttledUpdate([profileData2]); // Ignored if within 1 second
throttledUpdate([profileData3]); // Ignored if within 1 second
// Example 2: Scroll event optimization
final throttledScroll = onScroll.throttled(
const Duration(milliseconds: 16), // ~60 FPS
leading: true, // Execute immediately
trailing: true, // Execute final call after delay
);
// Example 3: Expensive calculations
final throttledCalculation = Throttle(
performHeavyCalculation,
const Duration(milliseconds: 500),
);
๐พ Data Structures & Algorithms #
๐๏ธ LRU Cache #
Efficient Least Recently Used cache with automatic eviction and O(1) operations
An high-performance LRU cache implementation that automatically manages memory by evicting least recently used items when capacity is reached.
๐ฏ Key Features
- โก O(1) Performance for get, set, and delete operations
- ๐ Automatic Eviction of least recently used items
- ๐ Customizable Capacity with efficient memory management
- ๐ Standard Map Interface for familiar usage patterns
๐ Usage Examples
import 'package:dart_suite/dart_suite.dart';
// Example 1: Basic LRU cache usage
final cache = LruCache<String, int>(capacity: 3);
cache['a'] = 1;
cache['b'] = 2;
cache['c'] = 3;
print(cache['a']); // 1 (moves 'a' to most recently used)
cache['d'] = 4; // Evicts 'b' (least recently used)
print(cache.containsKey('b')); // false
print(cache.keys); // ['c', 'a', 'd'] (most recent last)
// Example 2: Cache with complex objects
final userCache = LruCache<int, User>(capacity: 100);
userCache[123] = User(id: 123, name: 'John');
final user = userCache[123]; // Quick O(1) retrieval
// Example 3: Image caching scenario
final imageCache = LruCache<String, ImageData>(capacity: 50);
ImageData? getCachedImage(String url) {
return imageCache[url];
}
void cacheImage(String url, ImageData image) {
imageCache[url] = image;
}
// Example 4: With null safety
final safeCache = LruCache<String, String?>(capacity: 10);
safeCache['key'] = null; // Explicitly cache null values
๐ Performance Characteristics
| Operation | Time Complexity | Description |
|---|---|---|
get(key) |
O(1) | Retrieve and mark as recently used |
put(key, value) |
O(1) | Insert/update and manage capacity |
remove(key) |
O(1) | Remove specific key |
containsKey(key) |
O(1) | Check existence without affecting order |
๐ก Use Cases
- Image/Asset Caching - Limit memory usage in mobile apps
- API Response Caching - Store recent network responses
- Computed Values - Cache expensive calculations
- User Session Data - Maintain recent user interactions
๐งฎ LCM & GCD #
Mathematical utilities for Least Common Multiple and Greatest Common Divisor calculations
Built-in support for fundamental mathematical operations commonly needed in algorithms and mathematical computations.
import 'package:dart_suite/dart_suite.dart';
// GCD (Greatest Common Divisor)
int result1 = gcd(48, 18); // 6
int result2 = gcd(17, 13); // 1 (coprime numbers)
// LCM (Least Common Multiple)
int result3 = lcm(12, 8); // 24
int result4 = lcm(7, 5); // 35
// Use in real scenarios
int findOptimalBatchSize(int itemCount, int containerSize) {
return lcm(itemCount, containerSize) ~/ itemCount;
}
๐ Utility Typedefs #
Lightweight, type-safe, and expressive type definitions without boilerplate classes
๐ Why Use These Typedefs? #
- โ Clean & descriptive code without extra class definitions
- ๐ Type-safe alternative to raw Maps or Lists
- ๐จ๏ธ Easy to serialize & debug (records print beautifully)
- ๐ Seamless integration with Dart & Flutter projects
- ๐ฏ Zero runtime overhead - pure compile-time types
๐ Geometry & Spatial #
Perfect for 2D/3D graphics, mapping, and spatial calculations:
// Basic 2D and 3D points
Point2D location = (x: 100.0, y: 200.0);
Point3D position = (x: 1.0, y: 2.0, z: 3.0);
// Geographic coordinates
GeoCoordinate userLocation = (lat: 37.7749, lng: -122.4194); // San Francisco
GeoCoordinate3D preciseLocation = (
lat: 37.7749,
lng: -122.4194,
alt: 52.0, // altitude in meters
acc: 5.0 // accuracy in meters (optional)
);
// Dimensions for layouts and measurements
Dimension boxSize = (length: 10.0, width: 5.0, height: 3.0);
RectBounds viewport = (x: 0, y: 0, width: 1920, height: 1080);
๐ Data Structures #
Lightweight data containers for common programming patterns:
// JSON handling made simple
JSON<String> config = {'theme': 'dark', 'language': 'en'};
JSON<int> scores = {'alice': 95, 'bob': 87, 'charlie': 92};
// Key-value pairs
JSON_1<String> setting = (key: 'theme', value: 'dark');
Pair<String, int> nameAge = (first: 'Alice', second: 30);
Triple<String, String, int> fullRecord = (first: 'Alice', second: 'Smith', third: 30);
// Common domain objects
IdName category = (id: 'tech', name: 'Technology');
Pagination pageInfo = (page: 1, pageSize: 20, totalCount: 100);
๐ ๏ธ Utility & Domain-Oriented #
Specialized types for common application domains:
// Color representations
RGB primaryColor = (r: 255, g: 100, b: 50);
RGBA transparentColor = (r: 255, g: 255, b: 255, a: 0.8);
// UI and layout
RectBounds modalBounds = (x: 100, y: 50, width: 300, height: 200);
Dimension screenSize = (length: 1920, width: 1080, height: 1);
// Pagination for APIs
Pagination getUsersPage(int page) => (
page: page,
pageSize: 25,
totalCount: null // Will be filled by API response
);
โ Java-like Functional Typedefs #
Bringing familiar functional programming patterns from Java to Dart:
๐ Predicates & Conditions
// Simple predicates
Predicate<int> isEven = (n) => n % 2 == 0;
Predicate<String> isNotEmpty = (s) => s.isNotEmpty;
// Bi-predicates for comparisons
BiPredicate<int, int> isGreater = (a, b) => a > b;
BiPredicate<String, String> startsWith = (text, prefix) => text.startsWith(prefix);
// Usage in filtering
List<int> numbers = [1, 2, 3, 4, 5, 6];
List<int> evenNumbers = numbers.where(isEven).toList();
๐ Functions & Operators
// Consumers for side effects
Consumer<String> logger = (message) => print('[LOG] $message');
BiConsumer<String, int> logWithLevel = (message, level) =>
print('[L$level] $message');
// Suppliers for lazy values
Supplier<DateTime> currentTime = () => DateTime.now();
Supplier<String> randomId = () => Uuid().v4();
// Operators for transformations
UnaryOperator<String> toUpperCase = (s) => s.toUpperCase();
BinaryOperator<int> add = (a, b) => a + b;
BinaryOperator<String> concat = (a, b) => '$a$b';
๐ฏ Advanced Functional Types
// Comparators for sorting
Comparator<Person> byAge = (p1, p2) => p1.age.compareTo(p2.age);
Comparator<String> byLength = (a, b) => a.length.compareTo(b.length);
// Throwing functions for error handling
ThrowingSupplier<String> readFile = () {
// Might throw IOException
return File('config.txt').readAsStringSync();
};
ThrowingFunction<String, int> parseNumber = (str) {
// Might throw FormatException
return int.parse(str);
};
// Callables and Runnables
Runnable cleanup = () => tempFiles.clear();
Callable<bool> validate = () => form.isValid();
๐ก Real-World Example
import 'package:dart_suite/dart_suite.dart';
// API service with pagination and error handling
Future<List<GeoCoordinate>> fetchLocations(String url, Pagination pageInfo) async {
final response = await http.get('$url?page=${pageInfo.page}&size=${pageInfo.pageSize}');
return parseLocations(response.body);
}
// Cache fallback function
List<GeoCoordinate>? getCachedLocations(String url, Pagination pageInfo) {
return locationCache['${url}_${pageInfo.page}_${pageInfo.pageSize}'];
}
void main() async {
Pagination pageInfo = (page: 1, pageSize: 20, totalCount: null);
String apiUrl = "https://api.example.com/locations";
// Use asyncGuard for automatic fallback to cache
final locations = await asyncGuard(
() => fetchLocations(apiUrl, pageInfo),
def: getCachedLocations(apiUrl, pageInfo) ?? <GeoCoordinate>[],
onError: (e) => print('API failed, using cache: $e'),
);
// Process locations with functional approach
Consumer<GeoCoordinate> logLocation = (coord) =>
print('Location: ${coord.lat}, ${coord.lng}');
locations.forEach(logLocation);
}
๐ก Important Notes #
โ ๏ธ These typedefs complement, not replace, Flutter's core types:
- Use Flutter's
Size,Offset,Rectfor UI positioning- Use
DateTimeRangefor date ranges in Flutter apps- These typedefs are ideal for data modeling, JSON mapping, and utility functions
โฐ Time Utilities #
๐ Timeago #
Typedefs Toolkit #
Lightweight, type-safe, and expressive typedefs for Dart & Flutter. No boilerplate classes needed!
Why use this? #
- Clean & descriptive code without extra class definitions
- Type-safe alternative to raw Maps or Lists
- Easy to serialize & debug (records print nicely)
- Works seamlessly in Dart & Flutter projects
๐ Categories & Typedefs #
๐ Geometry & Spatial
Point2Dโ(x, y)Point3Dโ(x, y, z)GeoCoordinateโ(lat, lng)GeoCoordinate3Dโ(lat, lng, alt, acc?)Dimensionโ(length, width, height)
๐ Data Structures
JSON<T>โMap<String, T>JSON_1<T>โ(key, value)Pair<A, B>โ(first, second)Triple<A, B, C>โ(first, second, third)
๐ Utility & Domain-Oriented
IdNameโ(id, name)RGBโ(r, g, b)RGBAโ(r, g, b, a)RectBoundsโ(x, y, width, height)Paginationโ(page, pageSize, totalCount?)
โ Java-like Functional Typedefs
Predicate<T>โbool Function(T t)BiPredicate<T, U>โbool Function(T t, U u)Consumer<T>โvoid Function(T t)BiConsumer<T, U>โvoid Function(T t, U u)Supplier<T>โT Function()UnaryOperator<T>โT Function(T operand)BinaryOperator<T>โT Function(T left, T right)Runnableโvoid Function()Callable<V>โV Function()Comparator<T>โint Function(T o1, T o2)ThrowingConsumer<T>โvoid Function(T t)ThrowingSupplier<T>โT Function()ThrowingFunction<T, R>โR Function(T t)
๐ Usage Examples #
import 'package:dart_suite/dart_suite.dart';
Future<List<GeoCoordinate>> fetchFromServer(String url, Pagination pageInfo) {
... // fetch data using pageInfo.page and pageInfo.pageSize
}
List<GeoCoordinate>? getFromCache(String url, Pagination pageInfo) {
... // get data using pageInfo.page and pageInfo.pageSize
}
void main() {
Pagination pageInfo = (page: 1, pageSize: 20, totalCount: 95);
String apiUrl = "https://api.example.com/locations";
// Use asyncGuard to fall back to cache on error
final data = await asyncGuard(
() => fetchFromServer(apiUrl, pageInfo),
def: getFromCache(apiUrl, pageInfo),
onError: (e) => print('Fetch failed, using cache: $e'),
);
}
๐ก Notes #
- These typedefs are not replacements for Flutterโs core types like
Size,Offset,Rect, orDateTimeRange. - They are designed for lightweight data modeling, JSON mapping, and utility/functional programming use cases.
- You can extend them with helper methods using extensions for added functionality.
Timeago #
Human-readable time differences with internationalization support
Transform timestamps into user-friendly relative time strings like "2 hours ago", "just now", or "in 3 days" with full localization support.
๐ฏ Key Features
- ๐ Multi-language Support with built-in locales
- ๐จ Flexible Formatting with full/abbreviated modes
- ๐ Smart Date Handling with year formatting options
- โก Performance Optimized for frequent updates
๐ Usage Examples
import 'package:dart_suite/dart_suite.dart';
// Example 1: Basic timeago usage
var pastDate = DateTime(2020, 6, 10);
var timeago1 = Timeago.since(pastDate); // English (default)
var timeago2 = Timeago.since(pastDate, code: 'hi'); // Hindi
print(timeago1.format()); // "3y"
print(timeago1.format(isFull: true)); // "3 years ago"
print(timeago2.format(isFull: true)); // "3 เคตเคฐเฅเคทเฅเค เคชเฅเคฐเฅเคต"
// Example 2: Advanced formatting with date fallback
var oldDate = DateTime(2012, 6, 10);
var timeago = Timeago.since(oldDate);
print(timeago.format(isFull: true, yearFormat: (date) => date.yMMMEd()));
// Output: "Sat, Jun 10, 2012" (switches to date format for old dates)
// Example 3: Real-time updates
class CommentWidget extends StatefulWidget {
final DateTime createdAt;
Widget build(BuildContext context) {
final timeago = Timeago.since(createdAt);
return Text(timeago.format(isFull: true));
}
}
// Example 4: Different locales
final dates = [
DateTime.now().subtract(Duration(minutes: 5)),
DateTime.now().subtract(Duration(hours: 2)),
DateTime.now().subtract(Duration(days: 1)),
];
for (final date in dates) {
final en = Timeago.since(date, code: 'en');
final hi = Timeago.since(date, code: 'hi');
final es = Timeago.since(date, code: 'es');
print('EN: ${en.format(isFull: true)}');
print('HI: ${hi.format(isFull: true)}');
print('ES: ${es.format(isFull: true)}');
}
๐ Supported Languages
| Code | Language | Example Output |
|---|---|---|
en |
English | "2 hours ago", "in 3 days" |
hi |
Hindi | "2 เคเคเคเฅ เคชเฅเคฐเฅเคต", "3 เคฆเคฟเคจเฅเค เคฎเฅเค" |
es |
Spanish | "hace 2 horas", "en 3 dรญas" |
fr |
French | "il y a 2 heures", "dans 3 jours" |
de |
German | "vor 2 Stunden", "in 3 Tagen" |
๐ Format Options
final timeago = Timeago.since(DateTime.now().subtract(Duration(hours: 2)));
// Abbreviated format
print(timeago.format()); // "2h"
// Full format
print(timeago.format(isFull: true)); // "2 hours ago"
// With custom year formatting
print(timeago.format(
isFull: true,
yearFormat: (date) => DateFormat.yMMMMd().format(date)
)); // "June 10, 2023" for dates over a year old
๐ก Use Cases
- Social Media Feeds - Show post/comment timestamps
- Chat Applications - Display message times
- Activity Logs - Track user actions
- Content Management - Show last modified dates
๐ค Text Processing #
๐ ReCase #
Convert strings between different case formats with ease
Transform text between camelCase, snake_case, PascalCase, and many other formats for consistent naming conventions.
๐ฏ Key Features
- ๐ Multiple Case Formats - Support for 10+ case types
- ๐ฏ Smart Recognition - Automatically detects source format
- ๐ Method Chaining - Fluent API for easy transformations
- ๐ Performance Optimized - Efficient string processing
๐ Available Cases
| Method | Input | Output | Use Case |
|---|---|---|---|
camelCase |
hello_world |
helloWorld |
JavaScript variables |
pascalCase |
hello_world |
HelloWorld |
Class names |
snakeCase |
HelloWorld |
hello_world |
Database columns |
constantCase |
HelloWorld |
HELLO_WORLD |
Environment variables |
dotCase |
HelloWorld |
hello.world |
File extensions |
paramCase |
HelloWorld |
hello-world |
URL slugs |
pathCase |
HelloWorld |
hello/world |
File paths |
sentenceCase |
hello_world |
Hello world |
UI text |
titleCase |
hello_world |
Hello World |
Headings |
headerCase |
hello_world |
Hello-World |
HTTP headers |
๐ Usage Examples
import 'package:dart_suite/dart_suite.dart';
// Example 1: API response transformation
Map<String, dynamic> apiResponse = {
'user_name': 'john_doe',
'email_address': '[email protected]',
'profile_image_url': 'https://...'
};
// Convert to Dart naming convention
Map<String, dynamic> dartFormat = apiResponse.map(
(key, value) => MapEntry(key.reCase.camelCase, value),
);
// Result: {'userName': 'john_doe', 'emailAddress': '[email protected]', ...}
// Example 2: Database to UI conversion
final databaseColumn = 'created_at_timestamp';
final uiLabel = databaseColumn.reCase.titleCase; // 'Created At Timestamp'
final constantName = databaseColumn.reCase.constantCase; // 'CREATED_AT_TIMESTAMP'
// Example 3: URL slug generation
final articleTitle = 'How to Build Amazing Flutter Apps';
final urlSlug = articleTitle.reCase.paramCase; // 'how-to-build-amazing-flutter-apps'
// Example 4: Code generation
final className = 'user_profile_service';
final dartClass = className.reCase.pascalCase; // 'UserProfileService'
final fileName = className.reCase.snakeCase; // 'user_profile_service'
final constantName = className.reCase.constantCase; // 'USER_PROFILE_SERVICE'
// Example 5: Multi-format processing
final input = 'some_complex_variable_name';
final recase = input.reCase;
print('Original: $input');
print('Camel: ${recase.camelCase}'); // someComplexVariableName
print('Pascal: ${recase.pascalCase}'); // SomeComplexVariableName
print('Constant: ${recase.constantCase}'); // SOME_COMPLEX_VARIABLE_NAME
print('Sentence: ${recase.sentenceCase}'); // Some complex variable name
print('Title: ${recase.titleCase}'); // Some Complex Variable Name
๐ RegPatterns #
Comprehensive regular expression patterns for common validation scenarios
A complete collection of pre-built regex patterns for emails, passwords, URLs, phone numbers, file formats, and more - all with built-in validation and error handling.
๐ฏ Key Features
- ๐ 50+ Predefined Patterns for common use cases
- ๐ Advanced Password Validation with customizable requirements
- ๐ฑ Phone & Email Validation with international support
- ๐ URL & Domain Patterns for web applications
- ๐ File Format Detection for uploads and processing
- ๐๏ธ Customizable Parameters for flexible validation
๐ General Patterns
import 'package:dart_suite/dart_suite.dart';
// Email validation with detailed checking
bool isValidEmail = '[email protected]'.regMatch(regPatterns.email);
print(isValidEmail); // true
// URL validation (HTTP, HTTPS, FTP)
bool isValidUrl = 'https://example.com/path?param=value'.regMatch(regPatterns.url);
// Phone number validation (international formats)
bool isValidPhone = '+1-234-567-8900'.regMatch(regPatterns.phoneNumber);
// Username validation with custom rules
final usernamePattern = regPatterns.username(
allowSpace: false,
allowSpecialChar: '_-',
minLength: 3,
maxLength: 16,
);
bool isValidUsername = 'john_doe_123'.regMatch(usernamePattern);
// Name validation (supports international characters)
bool isValidName = 'Josรฉ Marรญa Garcรญa'.regMatch(regPatterns.name);
๐ Password Patterns
Create secure password requirements with fine-grained control:
// Example 1: Enterprise-grade password
final strongPasswordPattern = regPatterns.password(
PasswordType.ALL_CHARS_UPPER_LOWER_NUM_SPECIAL,
minLength: 12,
maxLength: 128,
allowSpace: false,
);
bool isStrong = 'MySecure!Pass123'.regMatch(strongPasswordPattern);
// Example 2: User-friendly password
final basicPasswordPattern = regPatterns.password(
PasswordType.ALL_CHARS_UPPER_LOWER_NUM,
minLength: 8,
maxLength: 50,
allowSpace: true,
);
// Example 3: PIN-style password
final pinPattern = regPatterns.password(
PasswordType.ONLY_LETTER_NUM,
minLength: 4,
maxLength: 6,
);
// Available password types:
// - ALL_CHARS_UPPER_LOWER_NUM_SPECIAL (most secure)
// - ALL_CHARS_UPPER_LOWER_NUM (balanced)
// - ALL_CHAR_LETTER_NUM (alphanumeric)
// - ONLY_LETTER_NUM (simple)
// - ANY_CHAR (minimal restrictions)
๐ข Numeric Patterns
Validate various number formats with precision:
// Decimal numbers with custom separators
final decimalPattern = regPatterns.number(
type: Number.decimal,
allowEmptyString: false,
allowSpecialChar: '.,', // Allow both dots and commas
minLength: 1,
maxLength: 20,
);
// Examples that match:
'123.45'.regMatch(decimalPattern); // true
'1,234.56'.regMatch(decimalPattern); // true
'-99.99'.regMatch(decimalPattern); // true
// Binary numbers
'1010110'.regMatch(regPatterns.number(type: Number.binary)); // true
// Hexadecimal numbers
'0xFF42A1'.regMatch(regPatterns.number(type: Number.hexDecimal)); // true
// Octal numbers
'755'.regMatch(regPatterns.number(type: Number.octal)); // true
๐ File Format Patterns
Detect and validate file types by extension:
// Image files
bool isImage = 'photo.jpg'.regMatch(regPatterns.fileFormats.image);
bool isPNG = 'logo.png'.regMatch(regPatterns.fileFormats.image);
bool isWebP = 'modern.webp'.regMatch(regPatterns.fileFormats.image);
// Document files
bool isPDF = 'document.pdf'.regMatch(regPatterns.fileFormats.pdf);
bool isWord = 'report.docx'.regMatch(regPatterns.fileFormats.word);
bool isExcel = 'spreadsheet.xlsx'.regMatch(regPatterns.fileFormats.excel);
bool isPowerPoint = 'presentation.pptx'.regMatch(regPatterns.fileFormats.ppt);
// Media files
bool isAudio = 'music.mp3'.regMatch(regPatterns.fileFormats.audio);
bool isVideo = 'movie.mp4'.regMatch(regPatterns.fileFormats.video);
// Archive files
bool isZip = 'archive.zip'.regMatch(regPatterns.fileFormats.archive);
๐ Network & Security Patterns
// IP Address validation
bool isIPv4 = '192.168.1.1'.regMatch(regPatterns.ipv4);
bool isIPv6 = '2001:0db8:85a3::8a2e:0370:7334'.regMatch(regPatterns.ipv6);
// Credit card validation (basic format check)
bool isCreditCard = '4111-1111-1111-1111'.regMatch(regPatterns.creditCard);
// Base64 validation
bool isBase64 = 'SGVsbG8gV29ybGQ='.regMatch(regPatterns.base64);
// Date time validation
bool isDateTime = '2023-11-27 08:14:39.977'.regMatch(regPatterns.basicDateTime);
๐ Pattern Combination & Advanced Usage
// Check if string matches ANY pattern
final contactPatterns = {
regPatterns.email,
regPatterns.phoneNumber,
regPatterns.url,
};
bool isValidContact = '[email protected]'.regAnyMatch(contactPatterns); // true
bool isValidContact2 = '+1-555-0123'.regAnyMatch(contactPatterns); // true
bool isInvalidContact = 'not-valid-input'.regAnyMatch(contactPatterns); // false
// Check if string matches ALL patterns (rare but useful)
final strictPatterns = {
regPatterns.username(minLength: 5, maxLength: 15),
regPatterns.password(PasswordType.ALL_CHAR_LETTER_NUM, minLength: 5),
};
// Validation with error handling
try {
'invalid-email'.regMatch(regPatterns.email, throwError: true);
} catch (e) {
print('Email validation failed: $e');
}
// Custom error handling
'weak123'.regMatch(
regPatterns.password(PasswordType.ALL_CHARS_UPPER_LOWER_NUM_SPECIAL),
throwError: true,
onError: (e) => showUserFriendlyError('Password is too weak'),
);
๐ก Real-World Examples
// Form validation
class RegistrationForm {
bool validateEmail(String email) =>
email.regMatch(regPatterns.email);
bool validatePassword(String password) =>
password.regMatch(regPatterns.password(
PasswordType.ALL_CHARS_UPPER_LOWER_NUM_SPECIAL,
minLength: 8,
));
bool validateUsername(String username) =>
username.regMatch(regPatterns.username(
allowSpace: false,
minLength: 3,
maxLength: 20,
));
}
// File upload validation
bool canUploadFile(String filename) {
final allowedFormats = {
regPatterns.fileFormats.image,
regPatterns.fileFormats.pdf,
regPatterns.fileFormats.word,
};
return filename.regAnyMatch(allowedFormats);
}
// Data cleaning
List<String> cleanPhoneNumbers(List<String> rawNumbers) {
return rawNumbers
.where((number) => number.regMatch(regPatterns.phoneNumber))
.toList();
}
๐ URL Schemes #
๐ง Mailto #
Generate properly formatted mailto URLs with support for multiple recipients, subjects, and body content
Create email links that work across all platforms and email clients with proper encoding and validation.
๐ Usage Examples
import 'package:dart_suite/dart_suite.dart';
// Example 1: Simple email link
final simpleEmail = Mailto(to: ['[email protected]']).toString();
// Result: "mailto:[email protected]"
// Example 2: Complex email with all options
final detailedEmail = Mailto(
to: ['[email protected]', '[email protected]'],
cc: ['[email protected]'],
bcc: ['[email protected]'],
subject: 'Project Update - Q4 2023',
body: '''Dear Team,
Please find attached the quarterly report.
The key metrics show significant improvement.
Best regards,
Project Manager''',
).toString();
// Example 3: Support ticket template
final supportTicket = Mailto(
to: ['[email protected]'],
subject: 'Bug Report - App Version ${AppVersion.current}',
body: '''
Device: ${Platform.operatingSystem}
App Version: ${AppVersion.current}
Issue Description:
[Please describe the issue here]
Steps to Reproduce:
1.
2.
3.
Expected Behavior:
Actual Behavior:
''',
).toString();
// Example 4: Feedback form
Widget createFeedbackButton() {
return ElevatedButton(
onPressed: () async {
final feedbackEmail = Mailto(
to: ['[email protected]'],
subject: 'App Feedback',
body: 'I would like to share the following feedback:\n\n',
).toString();
if (await canLaunch(feedbackEmail)) {
await launch(feedbackEmail);
}
},
child: Text('Send Feedback'),
);
}
๐๏ธ Annotations & Code Generation #
๐ Singleton Annotation #
Automatic singleton pattern implementation with code generation
Generate clean singleton classes from private abstract classes with full constructor support and lazy initialization - works with gen_suite package for code generation.
๐ฏ Key Features
- ๐๏ธ Full Constructor Support - Works with default, positional, optional, and named parameters
- ๐ฏ Parameter Preservation - Maintains parameter shapes and default values
- โก Lazy Initialization - Static-backed instance creation only when needed
- ๐ Type Safety - Compile-time singleton pattern enforcement
- ๐ Clean Generation - Uses
code_builder+source_genfor readable output
๐ Usage Examples
// Step 1: Create your singleton service
import 'package:dart_suite/dart_suite.dart';
part 'my_service.g.dart'; // This file will be generated
@Singleton()
abstract class _ApiService {
// Constructor with various parameter types
_ApiService(
this.apiKey, // Required positional
this.baseUrl, { // Required positional
this.timeout = 30, // Named with default
this.retryCount = 3, // Named with default
this.debugMode = false, // Named with default
required this.userAgent, // Required named
}) {
print('ApiService initialized with key: ${apiKey.substring(0, 8)}...');
_setupHttpClient();
}
final String apiKey;
final String baseUrl;
final int timeout;
final int retryCount;
final bool debugMode;
final String userAgent;
late final HttpClient _client;
void _setupHttpClient() {
_client = HttpClient()
..connectionTimeout = Duration(seconds: timeout)
..userAgent = userAgent;
}
Future<Map<String, dynamic>> get(String endpoint) async {
// Implementation here
return {};
}
Future<Map<String, dynamic>> post(String endpoint, Map<String, dynamic> data) async {
// Implementation here
return {};
}
}
// Step 2: Run code generation
// In your terminal:
dart run build_runner build
// This generates my_service.g.dart with:
class ApiService extends _ApiService {
static ApiService? _instance;
ApiService._(
super.apiKey,
super.baseUrl, {
super.timeout = 30,
super.retryCount = 3,
super.debugMode = false,
required super.userAgent,
});
static ApiService getInstance(
String apiKey,
String baseUrl, {
int timeout = 30,
int retryCount = 3,
bool debugMode = false,
required String userAgent,
}) {
return _instance ??= ApiService._(
apiKey,
baseUrl,
timeout: timeout,
retryCount: retryCount,
debugMode: debugMode,
userAgent: userAgent,
);
}
}
// Step 3: Use your singleton
void main() {
// First call creates the instance
final apiService1 = ApiService.getInstance(
'your-api-key-here',
'https://api.example.com',
timeout: 45,
userAgent: 'MyApp/1.0',
);
// Subsequent calls return the same instance
final apiService2 = ApiService.getInstance(
'any-key', // These parameters are ignored
'any-url', // after first initialization
timeout: 999, //
userAgent: 'ignored', //
);
print(identical(apiService1, apiService2)); // true
// Use the service
await apiService1.get('/users');
await apiService2.post('/users', {'name': 'John'});
}
๐ง Advanced Examples
// Complex singleton with dependency injection
@Singleton()
abstract class _DatabaseService {
_DatabaseService({
required this.connectionString,
this.maxConnections = 10,
this.enableLogging = true,
List<String>? initialTables,
}) : _tables = initialTables ?? ['users', 'settings'] {
_initializeDatabase();
}
final String connectionString;
final int maxConnections;
final bool enableLogging;
final List<String> _tables;
late final Database _db;
void _initializeDatabase() {
print('Initializing database with ${_tables.length} tables');
// Database setup logic
}
Future<List<Map<String, dynamic>>> query(String sql) async {
// Implementation
return [];
}
}
// Configuration singleton
@Singleton()
abstract class _AppConfig {
_AppConfig.production() :
apiUrl = 'https://api.prod.example.com',
debugMode = false,
cacheSize = 1000;
_AppConfig.development() :
apiUrl = 'https://api.dev.example.com',
debugMode = true,
cacheSize = 100;
final String apiUrl;
final bool debugMode;
final int cacheSize;
String get environment => debugMode ? 'development' : 'production';
}
โ ๏ธ Important Notes
- Private Classes Only: The annotation only works with private abstract classes (starting with
_) - No Generics: Generic classes are not supported by the generator
- Single Constructor: Each class should have only one constructor
- Part Files: Always include the
part 'filename.g.dart';directive
๐ Benefits
- Memory Efficient: Only one instance per singleton class
- Thread Safe: Generated code handles concurrent access properly
- Developer Friendly: Maintains your constructor's signature exactly
- Error Prevention: Compile-time checks prevent singleton pattern mistakes
๐ง Validation #
โ ValidatorMixin #
Centralized validation logic for domain objects with safe exception handling
A lightweight mixin that enforces validation patterns and provides safe validation checking without repetitive try-catch blocks.
๐ฏ Key Features
- ๐ Enforced Validation - Requires implementation of
validator()method - ๐ก๏ธ Safe Validation - Built-in exception handling with
isValid() - ๐๏ธ Flexible Error Handling - Optional error callbacks and re-throwing
- ๐งช Testing Friendly - Debug-friendly validation with detailed errors
๐ Core Concepts
Classes mixing ValidatorMixin must implement:
validator()- Protected method containing validation logic- Throw exceptions for invalid states
The mixin provides:
isValid()- Safe validation that returnstrue/false- Built-in exception handling via
guardSafe - Optional error callbacks and re-throwing for debugging
๐ Usage Examples
import 'package:dart_suite/dart_suite.dart';
// Example 1: Simple domain object validation
class PersonName with ValidatorMixin {
final String firstName;
final String lastName;
const PersonName({
required this.firstName,
required this.lastName,
});
@override
void validator() {
if (firstName.isEmpty) throw ValidationException('First name cannot be empty');
if (lastName.isEmpty) throw ValidationException('Last name cannot be empty');
if (firstName.length < 2) throw ValidationException('First name too short');
if (lastName.length < 2) throw ValidationException('Last name too short');
}
}
// Usage
final validName = PersonName(firstName: 'John', lastName: 'Doe');
final invalidName = PersonName(firstName: '', lastName: 'Doe');
print(validName.isValid()); // true
print(invalidName.isValid()); // false
// Example 2: Email validation with regex
class Email with ValidatorMixin {
final String address;
const Email(this.address);
@override
void validator() {
if (!address.regMatch(regPatterns.email)) {
throw ValidationException('Invalid email format: $address');
}
}
}
final email1 = Email('[email protected]');
final email2 = Email('invalid-email');
print(email1.isValid()); // true
print(email2.isValid()); // false
// Example 3: Complex business object validation
class BankAccount with ValidatorMixin {
final String accountNumber;
final String routingNumber;
final double balance;
final String accountType;
const BankAccount({
required this.accountNumber,
required this.routingNumber,
required this.balance,
required this.accountType,
});
@override
void validator() {
// Account number validation
if (accountNumber.length < 8 || accountNumber.length > 17) {
throw ValidationException('Account number must be 8-17 digits');
}
if (!RegExp(r'^\d+$').hasMatch(accountNumber)) {
throw ValidationException('Account number must contain only digits');
}
// Routing number validation (US format)
if (routingNumber.length != 9) {
throw ValidationException('Routing number must be exactly 9 digits');
}
if (!RegExp(r'^\d+$').hasMatch(routingNumber)) {
throw ValidationException('Routing number must contain only digits');
}
// Balance validation
if (balance < 0 && accountType != 'credit') {
throw ValidationException('Balance cannot be negative for $accountType account');
}
// Account type validation
final validTypes = ['checking', 'savings', 'credit', 'investment'];
if (!validTypes.contains(accountType.toLowerCase())) {
throw ValidationException('Invalid account type: $accountType');
}
}
}
// Usage with error handling
final account = BankAccount(
accountNumber: '1234567890',
routingNumber: '987654321',
balance: 1500.50,
accountType: 'checking',
);
// Safe validation
if (account.isValid()) {
print('Account is valid');
} else {
print('Account validation failed');
}
// Validation with error logging
bool isAccountValid = account.isValid(
onError: (error) => print('Validation error: $error'),
);
// Debug validation (re-throws for detailed error info)
try {
bool isValid = account.isValid(reThrow: true);
} catch (e) {
print('Detailed validation error: $e');
}
๐งช Advanced Usage Patterns
// Example 4: Nested validation
class Address with ValidatorMixin {
final String street;
final String city;
final String zipCode;
final String country;
const Address({
required this.street,
required this.city,
required this.zipCode,
required this.country,
});
@override
void validator() {
if (street.isEmpty) throw ValidationException('Street is required');
if (city.isEmpty) throw ValidationException('City is required');
if (!zipCode.regMatch(regPatterns.number(type: Number.decimal))) {
throw ValidationException('Invalid zip code format');
}
if (country.length != 2) throw ValidationException('Country must be 2-letter code');
}
}
class User with ValidatorMixin {
final PersonName name;
final Email email;
final Address address;
final int age;
const User({
required this.name,
required this.email,
required this.address,
required this.age,
});
@override
void validator() {
// Validate nested objects
if (!name.isValid(reThrow: true)) return; // Will throw if invalid
if (!email.isValid(reThrow: true)) return;
if (!address.isValid(reThrow: true)) return;
// Validate own properties
if (age < 0 || age > 150) {
throw ValidationException('Invalid age: $age');
}
}
}
// Example 5: Conditional validation
class Product with ValidatorMixin {
final String name;
final double price;
final String category;
final bool isDigital;
final double? weight; // Only required for physical products
const Product({
required this.name,
required this.price,
required this.category,
required this.isDigital,
this.weight,
});
@override
void validator() {
if (name.isEmpty) throw ValidationException('Product name is required');
if (price <= 0) throw ValidationException('Price must be positive');
if (category.isEmpty) throw ValidationException('Category is required');
// Conditional validation based on product type
if (!isDigital && weight == null) {
throw ValidationException('Weight is required for physical products');
}
if (!isDigital && weight! <= 0) {
throw ValidationException('Weight must be positive for physical products');
}
if (isDigital && weight != null) {
throw ValidationException('Digital products should not have weight');
}
}
}
๐ก Best Practices
| โ Do | โ Don't |
|---|---|
Keep validator() methods focused and lightweight |
Perform side effects (network calls, file I/O) |
Use specialized helpers like regMatch for validation |
Write large validation methods with complex logic |
| Throw descriptive exceptions with helpful messages | Throw generic exceptions without context |
Use reThrow: true during testing for debugging |
Ignore validation errors in production |
| Compose validation by calling other validators | Duplicate validation logic across classes |
๐ง Integration with Forms
// Form integration example
class RegistrationFormValidator with ValidatorMixin {
final String username;
final String email;
final String password;
final String confirmPassword;
const RegistrationFormValidator({
required this.username,
required this.email,
required this.password,
required this.confirmPassword,
});
@override
void validator() {
// Use existing patterns
if (!username.regMatch(regPatterns.username(minLength: 3, maxLength: 20))) {
throw ValidationException('Invalid username format');
}
if (!email.regMatch(regPatterns.email)) {
throw ValidationException('Invalid email format');
}
if (!password.regMatch(regPatterns.password(
PasswordType.ALL_CHARS_UPPER_LOWER_NUM_SPECIAL,
minLength: 8
))) {
throw ValidationException('Password does not meet requirements');
}
if (password != confirmPassword) {
throw ValidationException('Passwords do not match');
}
}
}
// Usage in Flutter
class RegistrationForm extends StatefulWidget {
@override
_RegistrationFormState createState() => _RegistrationFormState();
}
class _RegistrationFormState extends State<RegistrationForm> {
String? _validationError;
void _validateForm() {
final validator = RegistrationFormValidator(
username: _usernameController.text,
email: _emailController.text,
password: _passwordController.text,
confirmPassword: _confirmPasswordController.text,
);
setState(() {
_validationError = null;
});
final isValid = validator.isValid(
onError: (error) => setState(() => _validationError = error.toString()),
);
if (isValid) {
_submitForm();
}
}
}
๐ Extensions #
Powerful utility extensions that enhance Dart's built-in types and functionality
Dart Suite includes comprehensive extensions for strings, collections, numbers, dates, and more to make your code more expressive and concise.
๐ค String Extensions
- Case conversions with
reCase - Validation with
regMatch,regAnyMatch,regAllMatch - Parsing utilities for safer type conversions
- Text manipulation helpers
๐ Collection Extensions
- Iterable enhancements for filtering, mapping, and grouping
- Map utilities for safer access and transformation
- Set operations for mathematical set operations
- List helpers for common operations
๐ข Numeric Extensions
- Mathematical operations like GCD, LCM
- Range checks and boundary utilities
- Formatting helpers for display
โฐ DateTime Extensions
- Relative time with Timeago integration
- Date arithmetic and comparison utilities
- Formatting shortcuts for common patterns
๐ฏ Optional Extensions
- Null safety helpers with
Optional<T> - Safe chaining operations
- Default value handling
๐ค Contributing #
We welcome contributions to make Dart Suite even better! Here's how you can help:
๐ Reporting Issues
- Use our issue tracker
- Provide detailed reproduction steps
- Include version information and error logs
๐ง Contributing Code
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Write tests for your changes
- Ensure all tests pass:
dart test - Submit a pull request with clear description
๐ Improving Documentation
- Help improve examples and explanations
- Add real-world use cases
- Fix typos and unclear sections
๐ก Suggesting Features
- Open an issue with the
enhancementlabel - Describe your use case clearly
- Provide examples of how it would work
๐ License #
This project is licensed under the MIT License - see the LICENSE file for details.
๐ Support & Community #
- ๐ Issues: GitHub Issues
- ๐ Documentation: API Reference
- ๐ฌ Discussions: GitHub Discussions
- โญ Show Support: Star on GitHub
Made with โค๏ธ by Rahul Sharma
If this package has been helpful, consider giving it a โญ on GitHub!
