utills 2.0.0
utills: ^2.0.0 copied to clipboard
A comprehensive Flutter utility package providing layout gaps, form validators, a generic API handler, and a scroll-ready paginator.
Utills - Flutter/Dart Utilities Package #
A comprehensive Dart/Flutter utility package that provides reusable components to accelerate application development. This package includes structured failure handling, form validators, UI spacing utilities, pagination, and API response handling.
Read the article on my Portfolio
Supported Platform: #
Android, IOS, Web, Windows, macOS, Linux
📋 Table of Contents #
🎯 Overview #
Utills is designed to eliminate repetitive boilerplate code in Flutter/Dart applications. Whether you're building forms, handling API responses, managing pagination, or creating consistent UI spacing, this package provides battle-tested utilities that work out of the box.
Key Benefits:
- ✅ Type-safe failure handling
- ✅ Pre-built form validators with validation messages
- ✅ Consistent UI spacing constants
- ✅ Efficient pagination for large lists
- ✅ Zero external dependencies (Flutter only)
📦 Installation #
Add utills to your pubspec.yaml:
dependencies:
utills: ^1.0.0
Then import the package in your Dart files:
import 'package:utills/utills.dart';
🚀 Features #
1. Gaps (Spacing) #
Predefined SizedBox constants for consistent vertical and horizontal spacing throughout your UI.
Available Gap Sizes:
| Constant | Size | Type |
|---|---|---|
vPad5, vPad10, vPad15, vPad20, vPad35 |
5-35px | Vertical |
hPad5, hPad10, hPad15, hPad20, hPad35 |
5-35px | Horizontal |
defaultPadding |
16px | Padding constant |
defaultRadius |
6px | Border radius constant |
Example:
import 'package:utills/gaps.dart';
Column(
children: [
Text("Title", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
vPad10, // 10px vertical spacing
Text("Subtitle", style: TextStyle(fontSize: 14)),
vPad20, // 20px vertical spacing
ElevatedButton(onPressed: () {}, child: Text("Submit")),
],
);
Row(
children: [
Icon(Icons.email),
hPad10, // 10px horizontal spacing
Text("user@example.com"),
],
);
// Use defaultPadding for consistent padding
Container(
padding: EdgeInsets.all(defaultPadding), // 16px padding
child: Text("Content"),
)
2. Validators #
Form field validators to handle common validation scenarios with user-friendly error messages.
Available Validators:
| Validator | Purpose |
|---|---|
fieldRequired() |
Ensures field is not empty |
emailValidator() |
Validates email format |
passwordRequired() |
Validates password with minimum length |
confirmPasswordRequired() |
Matches password confirmation |
usernameValidator() |
Validates username (alphanumeric + underscore) |
phoneValidator() |
Validates phone number format |
positiveNumberRequired() |
Ensures positive number input |
integerValidator() |
Validates integer input |
minLength() |
Checks minimum character length |
maxLength() |
Checks maximum character length |
Example:
import 'package:utills/validators.dart';
// In a TextFormField
TextFormField(
decoration: InputDecoration(labelText: "Email"),
validator: CommonValidator.emailValidator,
// Returns "Email is required" or "Enter a valid email address" on error
),
TextFormField(
decoration: InputDecoration(labelText: "Username"),
validator: CommonValidator.usernameValidator,
// Ensures 3+ characters with only letters, numbers, and underscores
),
3. API Handler & Failure Types #
Structured, type-safe handling of API responses and errors using a Failure class and helper functions.
Core Concepts:
Attempt<T>: A tuple type(T?, Failure?)representing either a successful value or a failuresuccess<T>(value): Helper to create a successful attemptfailed<T>(failure): Helper to create a failed attemptFailure<C>: Generic failure class with title, description, and code
Built-in Failure Types:
| Failure Type | HTTP Code | Use Case |
|---|---|---|
ServerFailure |
500 | Server-side errors |
InternetFailure |
~ | No internet connection |
TimeoutFailure |
408 | Request timeout |
UnknownFailure |
-1 | Unexpected errors |
SessionExpired |
401 | User session expired |
UnauthorizedFailure |
401 | User not authorized |
ForbiddenFailure |
403 | Resource access forbidden |
InvalidCredentialsFailure |
~ | Wrong username/password |
BadRequestFailure |
400 | Invalid request parameters |
Example:
import 'package:utills/api_handler.dart';
import 'package:utills/failure.dart';
// Define a function that returns Attempt
Future<Attempt<User>> fetchUser(int id) async {
try {
final response = await http.get(Uri.parse('https://api.example.com/users/$id'));
if (response.statusCode == 200) {
final user = User.fromJson(jsonDecode(response.body));
return success(user);
} else if (response.statusCode == 401) {
return failed(SessionExpired());
} else if (response.statusCode == 403) {
return failed(ForbiddenFailure());
} else {
return failed(ServerFailure(code: response.statusCode));
}
}
}
4. Paginator #
Efficient pagination utilities for loading large lists incrementally. The package provides a lightweight, UI-friendly paginator (RestPaginator<T>) designed to work well with Flutter widgets and ChangeNotifier-based state management.
Key concepts
RestPageData<T>: simple container returned by yourgetPagecallback. Typically containsitemsand an optionaltotalCount.RestPaginator<T>: manages loaded pages, exposes loading state, and provides helpers to paginate, refresh and access loaded items.
API surface (common)
-
RestPageData<T>List<T> items— the items returned for the pageint? totalCount— optional total number of items available on server
-
RestPaginator<T>(constructor)RestPaginator({ required Future<RestPageData<T>> Function(int page, int pageSize) getPage, int pageSize = 20 })
Important properties:
int availableItemCount— number of items currently loadedint? totalItemCount— total items reported by server (nullable)bool isPaginating— whether a page load is in progressbool endReached— true when all items are loaded
Common methods:
Future<void> paginate(int pageNumber)— load the requested pageFuture<void> paginateNext()— convenience to load the next pageFuture<void> refresh()— clear loaded pages and load first pageT getItem(int index)— get an item by absolute indexvoid dispose()— clean up listeners/resources
Typical getPage signature (example implementation):
class RestPageData<T> {
final List<T> items;
final int? totalCount;
RestPageData({required this.items, this.totalCount});
}
// Example getPage implementation
Future<RestPageData<Map<String, dynamic>>> fetchUsersPage(int page, int pageSize) async {
final response = await http.get(Uri.parse('https://api.example.com/users?page=$page&pageSize=$pageSize'));
final body = jsonDecode(response.body) as Map<String, dynamic>;
final items = (body['data'] as List).cast<Map<String, dynamic>>();
final total = body['total'] as int?;
return RestPageData(items: items, totalCount: total);
}
Using RestPaginator in a Flutter widget
final paginator = RestPaginator<Map<String, dynamic>>(
getPage: fetchUsersPage,
pageSize: 20,
);
// load first page (e.g., in initState)
await paginator.paginate(1);
// ListView with infinite scroll
ListView.builder(
itemCount: paginator.availableItemCount + (paginator.endReached ? 0 : 1),
itemBuilder: (context, index) {
if (index >= paginator.availableItemCount) {
// trigger load next page
paginator.paginateNext();
return const Center(child: CircularProgressIndicator());
}
final item = paginator.getItem(index);
return ListTile(title: Text(item['name'] ?? ''));
},
);
// Refreshing
await paginator.refresh();
// Dispose when no longer needed
paginator.dispose();
Notes and best practices
- Keep
getPageresilient: handle network errors and return an emptyRestPageDataor rethrow for the paginator to handle. - Use
paginateNext()in UI scroll handlers to keep the code simple. - Expose the paginator from a
ChangeNotifier(or provider) so UI widgets rebuild on state changes (e.g.,isPaginating,availableItemCount).
5. Log #
Developer-friendly logging utilities that print colorized messages in debug terminals and provide quick inspection and timing helpers.
What it provides:
- Colorized log helpers for common levels:
Log.success(),Log.error(),Log.info(),Log.warn(),Log.trace(). - All methods are no-ops in release builds (only active during development/debug).
Example usage:
import 'package:utills/utills.dart';
// Colored logging
Log.success('Operation completed'); // appears green in debug terminal
Log.error('Failed to save record', error: e); // appears red
Log.info('Loading user', data: {'id': 1});
Log.warn('Deprecated API used');
Log.trace('Deprecated API used');
6. CustomToast #
A lightweight, highly customizable Flutter package for displaying floating toast notifications with support for Success, Error, and Warning states.
⚙️ Initialization: To use CustomToast anywhere in your app without needing BuildContext, you must initialize it with your scaffoldMessengerKey in your main.dart.
final GlobalKey<ScaffoldMessengerState> messengerKey = GlobalKey<ScaffoldMessengerState>();
// optional
final myConfig = CustomToastConfig(
backgroundColors: {
ToastType.success: Colors.blueAccent,
ToastType.error: Colors.deepPurple,
ToastType.warning: Colors.amber,
},
borderRadius: BorderRadius.circular(20),
duration: const Duration(seconds: 2),
);
void main() {
// Initialize the package with default settings
CustomToast.init(messengerKey: messengerKey);
// Initialize with custom config
CustomToast.init(
messengerKey: messengerKey,
config: myConfig,
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
scaffoldMessengerKey: messengerKey, // <--- Crucial step
home: const HomeScreen(),
);
}
}
Usage: Trigger a toast notification from anywhere in your business logic or UI:
// Success Toast
CustomToast.show("Data saved successfully!", type: ToastType.success);
// Error Toast
CustomToast.show("Failed to connect to server.", type: ToastType.error);
// Warning Toast
CustomToast.show("Your subscription expires soon!", type: ToastType.warning);
Key Features - Decoupled Architecture: No BuildContext required for show() calls.
- Three Built-in States: Standardized support for success, error, and warning.
- Global Theming: Easily override default colors and styles to match your design system.
- Clean API: Optimized for readability and ease of use in large-scale applications.
7. Navigation Helper #
Simplify your routing logic with the Maps utility class. No more boilerplate MaterialPageRoute required.
Features:
-
Maps.push: Standard navigation to a new screen.
-
Maps.pushReplacement: Replace the current screen (e.g., after login).
-
Maps.pushAndRemoveUntil: Clear the stack and go to a new screen (e.g., after logout).
// Navigate to a new page
Navigate.push(context, const ProfileScreen());
// Replace the current page (Removes 'this' page from history)
Navigate.pushReplacement(context, const HomeScreen());
// Clear all routes and go to a new screen
Navigate.pushAndRemoveUntil(context, const WelcomeScreen());
This project is licensed under the MIT License - see the LICENSE file for details.
Usage Examples #
A single, compact example demonstrating the core utilities included in utills.
import 'package:flutter/material.dart';
import 'package:utills/utills.dart';
void main() async {
// Gaps: use constants in widgets
final spacing = vPad10;
// Validators
final emailError = CommonValidator.emailValidator('user@example.com');
// API handler / failures (illustrative)
final attempt = success({'id': 1, 'name': 'Alice'});
if (attempt.item != null) {
Log.success('Loaded user: ${attempt.item}');
} else {
Log.error('Failure: ${attempt.failure}');
}
// Paginator (simplified)
final paginator = RestPaginator<Map<String, dynamic>>(
getPage: (page, pageSize) async {
// Replace with real network call; return RestPageData(items, totalCount)
return RestPageData(items: List.generate(5, (i) => {'index': i}), totalCount: 100);
},
);
await paginator.paginate(1);
Log.info('Loaded ${paginator.availableItemCount} items');
// Log utilities
Log.inspect({'spacing': spacing});
}
Made with ❤️ for Flutter developers by Hasibul Hasan Tushar