dart_suite 0.0.7
dart_suite: ^0.0.7 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
- ๐ฆ Options & Null Safety
- โฐ 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
,Rect
for UI positioning- Use
DateTimeRange
for 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': 'john@example.com',
'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': 'john@example.com', ...}
// 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 = 'user@domain.com'.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 = 'john@example.com'.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: ['support@example.com']).toString();
// Result: "mailto:support@example.com"
// Example 2: Complex email with all options
final detailedEmail = Mailto(
to: ['john@example.com', 'jane@example.com'],
cc: ['manager@example.com'],
bcc: ['admin@example.com'],
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: ['support@yourapp.com'],
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: ['feedback@yourapp.com'],
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_gen
for 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(
String apiKey, // Required positional
String baseUrl, { // Required positional
int timeout = 30, // Named with default
int retryCount = 3, // Named with default
bool debugMode = false, // Named with default
String this.userAgent, // Required named
}) {
print('ApiService initialized with key: ${apiKey.substring(0, 8)}...');
_client = HttpClient()
..connectionTimeout = Duration(seconds: timeout)
..userAgent = userAgent;
}
late final HttpClient _client;
Future<Map<String, dynamic>> get(String endpoint) async {
if(APIService.I.baseUrl.isEmpty) { // Access via singleton instance
throw Exception("Base URL cannot be empty");
}
// Implementation here
return {};
}
bool get debugMode; // don't define it, it's auto-generated
Future<Map<String, dynamic>> post(String endpoint, Map<String, dynamic> data) async {
if(debugMode) { // Access via getter
//....do something
}
// Implementation here
return {};
}
}
// Step 2: Run code generation
// In your terminal:
dart run build_runner build
```dart
// Step 3: Use your singleton
void main() {
// First call creates the instance
final apiService1 = ApiService.init(
'your-api-key-here',
'https://api.example.com',
timeout: 45,
userAgent: 'MyApp/1.0',
);
// Subsequent calls return the same instance ๐
final apiService2 = ApiService.init(...);
print(identical(apiService1, apiService2)); // true
// Use the singleton instance
await ApiService.I.get('/users');
// Or via the instance
await apiService2.post('/users', {'name': 'John'});
}
โ ๏ธ 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('user@domain.com');
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');
}
๐ก 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();
}
}
}
๐ฆ Optional #
Replace dangerous null
with a principled, lightweight Optional<T>
. The API in this guide is grounded in the actual implementation of optional.dart
you shared (including its Present
/ Absent
types, Optional.Do
notation, map2 / map3
, collection helpers, and error/nullable combinators like tryCatch
, flatMapNullable
, flatMapThrowable
, etc.).
Import once, use everywhere:
import 'package:dart_suite/dart_suite.dart';
Why Optional? #
- ๐ก๏ธ Null safety by design โ Impossible to โforgetโ the empty case; the type system forces you to handle it.
- ๐ Fluent functional API โ
map
,flatMap
,filter
,filterMap
,ap
,map2/map3
,andThen
, etc. - ๐ก Explicit branching โ
match
/fold
keeps control flow obvious and safe. - ๐งฐ Interops cleanly โ Helpers for
T?
(Optional.of
,optional
,flatMapNullable
,get()
), thrown errors (tryCatch
,flatMapThrowable
), and collections (traverseList
,sequenceList
). - โก Tiny wrapper โ Minimal overhead;
Present
holds aT
,Absent
holds nothing.
Mental model #
Optional<T>
has two concrete states:
Present<T>(value)
โ contains aT
Absent
โ contains no value
Create values with constructors or helpers:
// Direct
final p = Present(42); // Present(42)
const n = Optional<int>.absent(); // Absent
// Idiomatic helpers (top-level)
final a = present(42); // Present(42)
final b = absent<int>(); // Absent
// From nullable + optional predicate
final o1 = Optional.of('John'); // Present('John')
final o2 = Optional.of(null); // Absent
final o3 = Optional.of(10, (x) => x > 5); // Present(10)
final o4 = Optional.of(3, (x) => x > 5); // Absent
// Convenience alias (just calls Optional.of)
final ox = optional('hi'); // Present('hi')
Core operations (the ones youโll use daily) #
// Pattern match (explicitly handle both branches)
final greeting = Optional.of('Alice').match(
() => 'Hello, stranger!',
(name) => 'Hello, $name!',
);
// map: T -> B
final len = Optional.of('abc').map((s) => s.length); // Present(3)
// flatMap: T -> Optional<B>
final email = Optional.of('john')
.flatMap((u) => Optional.of('$u@example.com')); // Present('john@example.com')
// filter: keep only if predicate is true
final adult = Optional.of(20).filter((n) => n >= 18); // Present(20)
final child = Optional.of(15).filter((n) => n >= 18); // Absent
// filterMap: T -> Optional<B> (filter + transform in one shot)
final parsed = Optional.of('123').filterMap(
(s) => Optional.tryCatch(() => int.parse(s)),
); // Present(123)
// Fallbacks
final v1 = Optional.of(10).orElse(() => 99); // 10 (lazy)
final v2 = Optional.absent<int>().orElse(() => 99); // 99
final v3 = Optional.of(10).orElseGet(99); // 10 (eager)
final v4 = Optional.absent<int>().orElseGet(99); // 99
// Boolean tests
final presentFlag = Optional.of(1).isPresent; // true
final absentFlag = Optional.absent<int>().isAbsent; // true
Applicative & multi-arg mapping #
ma**tch
vs fold
: fold(onAbsent, onPresent)
is an alias for match(onAbsent, onPresent)
. Use whichever you prefer.
ap
& pure
: ap
applies a function inside an Optional
to a value inside another Optional
.
double sumToDouble(int a, int b) => (a + b).toDouble();
final a = Optional.of(10);
final b = Optional.of(20);
// Turn `a` into an Optional function we can apply to `b`
final fn = a.map((x) => (int y) => sumToDouble(x, y));
// Apply it to `b`
final out = b.ap(fn); // Present(30.0)
map2
/ map3
#
Convenient when you need both values at once:
final fullName = Optional.of('Ada').map2(
Optional.of('Lovelace'),
(first, last) => '$first $last',
); // Present('Ada Lovelace')
final area = Optional.of(3).map3(
Optional.of(4),
Optional.of(5),
(a, b, c) => a * b * c,
); // Present(60)
andThen
#
Sequence without caring about the previous value:
final next = Optional.of('ignored').andThen(() => Optional.of(123)); // Present(123)
extend
/ duplicate
#
Operate on the container itself (advanced use):
final o = Optional.of(5);
final sz = o.extend((self) => self.isPresent ? 'has!' : 'none'); // Present('has!')
final dup = o.duplicate(); // Present(Present(5))
Interop with null
and exceptions (Nullable interop) #
// Build from nullable (and optional predicate)
final ok = Optional.of<int?>(42); // Present(42)
final no = Optional.of<int?>(null); // Absent
// Call a nullable-returning function in a flatMap
final nextNum = Optional.of('42')
.flatMapNullable((s) => int.tryParse(s)); // Present(42)
// Get back to nullable (prefer match/orElse when possible)
final String? maybe = Optional.of('x').get(); // 'x'
final String? none = Optional.absent<String>().get(); // null
Exceptions as values #
// Wrap a throwing computation
final parsed = Optional.tryCatch(() => int.parse('123')); // Present(123)
final failed = Optional.tryCatch(() => int.parse('oops')); // Absent
// Map with a function that may throw
final toInt = Optional.of('99').flatMapThrowable((s) => int.parse(s)); // Present(99)
traverseList
/ traverseListWithIndex
#
Map a list to Optional
s and collect all results, failing fast if any item is Absent
.
final inputs = ['1', '2', 'invalid', '4'];
Optional<List<int>> parsed = Optional.traverseList(inputs, (s) =>
Optional.tryCatch(() => int.parse(s)),
); // Absent (because 'invalid' fails)
final goods = ['1', '2', '3', '4'];
final okAll = Optional.traverseList(goods, (s) =>
Optional.tryCatch(() => int.parse(s)),
); // Present([1,2,3,4])
// Need indices in your mapping?
Optional.traverseListWithIndex(goods, (s, i) =>
(i % 2 == 0) ? Optional.tryCatch(() => int.parse(s))
: Optional.absent(),
); // Absent (because odd indexes absent)
sequenceList
#
Turn List<Optional<A>>
into Optional<List<A>>
:
final list = [Optional.of(1), Optional.of(2), Optional.of(3)];
final seq = Optional.sequenceList(list); // Present([1,2,3])
final withHole = [Optional.of(1), Optional.absent<int>(), Optional.of(3)];
final none = Optional.sequenceList(withHole); // Absent
Predicates & partitioning #
// Lift a predicate to Optional
final nonEmpty = Optional.fromPredicate('abc', (s) => s.isNotEmpty); // Present('abc')
// Map if predicate passes
final lengthWhenNonEmpty = Optional.fromPredicateMap<String, int>(
'abc',
(s) => s.isNotEmpty,
(s) => s.length,
); // Present(3)
// Split into (leftWhenFalse, rightWhenTrue)
final (left, right) = Optional.of(10).partition((n) => n.isEven);
// left = Absent, right = Present(10)
The โDo notationโ (ergonomic chaining) #
Optional.Do
lets you write sequential code and extract from Optional
s using a special adapter ($
). If any extracted value is Absent
, the whole block becomes Absent
automatically.
final res = Optional.Do(($) {
final user = $(findUserById(123)); // Optional<User>
final email = $(getVerifiedEmail(user)); // Optional<String>
final lower = email.toLowerCase();
return lower;
});
Under the hood it uses a private throw/catch on an adapter; thatโs exactly what the code in
optional.dart
implements.
Validation (no if
pyramids) #
class Registration {
final String email;
final String password;
final int age;
Registration({required this.email, required this.password, required this.age});
Optional<String> validateEmail() =>
Optional.fromPredicate(email, (e) => RegExp(r'^[^@]+@[^@]+\.[^@]+$').hasMatch(e));
Optional<String> validatePassword() =>
Optional.fromPredicate(password, (p) => p.length >= 8);
Optional<int> validateAge() =>
Optional.fromPredicate(age, (a) => a >= 13 && a <= 120);
Optional<Registration> validate() =>
validateEmail()
.andThen(validatePassword)
.andThen(validateAge)
.map((_) => this);
}
API cheat-sheet (as implemented) #
Creation
Present(value)
/const Optional.absent()
present<T>(t)
/absent<T>()
Optional.of([T? value, Predicate<T>? predicate])
optional([value, predicate])
(alias toOptional.of
)Optional.fromPredicate(value, predicate)
Optional.fromPredicateMap(value, predicate, f)
Optional.tryCatch(() => ...)
Optional.flatten(Optional<Optional<T>> m)
Core
match(onAbsent, onPresent)
/fold(onAbsent, onPresent)
map(f)
/flatMap(f)
/flatMapNullable(f)
/flatMapThrowable(f)
filter(pred)
/filterMap(f)
/partition(pred)
orElse(() => t)
/orElseGet(t)
/or(() => Optional<T>)
isPresent
/isAbsent
/get()
(โT?
)
Applicative & friends
ap(Optional<B Function(T)>)
/pure(b)
map2(other, (a, b) => ...)
map3(b, c, (a, b, c) => ...)
andThen(() => Optional<B>)
extend(f)
/duplicate()
Collections
Optional.traverseList(list, f)
Optional.traverseListWithIndex(list, (a, i) => ...)
Optional.sequenceList(list)
Do notation
Optional.Do(($) { final x = $(opt); ... return ...; })
Equality & debug
Present(a) == Present(b)
ifa == b
Absent == Absent
toString()
:Present(value)
/Absent
Best practices #
โ Do | โ Donโt |
---|---|
Use match /fold to handle both branches |
Call .get() and assume non-null |
Prefer flatMap to avoid nested Optional<Optional<T>> |
Create Optional<T?> unless you truly need nested nullability |
Use filter /filterMap instead of if chains |
Mix null and Optional haphazardly |
Wrap throwing code with tryCatch or flatMapThrowable |
Throw inside business logic and forget to catch |
Use traverseList/sequenceList for batch workflows |
Loop manually and forget to short-circuit on failure |
Reach for map2 /map3 for multi-input logic |
Chain deep pyramids of flatMap when combining many values |
๐ 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
enhancement
label - 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!