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, API response handling, animated toast notifications, and navigation helpers.

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
  • ✅ Animated toast notifications without passing BuildContext
  • ✅ Simple page navigation with optional slide transitions
  • ✅ Zero external dependencies (Flutter only)

📦 Installation

Add utills to your pubspec.yaml:

dependencies:
  utills: ^2.0.1

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

Example:

import 'package:utills/utills.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 normal Flutter padding with the gap constants
Container(
  padding: const EdgeInsets.all(16),
  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/utills.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 failure
  • success<T>(value): Helper to create a successful attempt
  • failed<T>(failure): Helper to create a failed attempt
  • Failure<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/utills.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>: record returned by your getPage callback. It contains items and totalPages.
  • RestPaginator<T>: manages loaded pages, exposes loading state, and provides helpers to paginate, refresh and access loaded items.
  • RestPaginatorWithExtra<T, E>: optional paginator variant for APIs that return extra metadata.

API surface (common)

  • RestPageData<T>

    • List<T> items — the items returned for the page
    • int totalPages — total pages 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 loaded
  • int? totalItemCount — estimated total items based on totalPages * pageSize
  • bool isPaginating — whether a page load is in progress
  • bool endReached — true when all items are loaded

Common methods:

  • Future<void> paginate(int pageNumber) — load the requested page
  • Future<void> refresh() — clear loaded pages and load first page
  • T getItem(int index) — get an item by absolute index and auto-load the next page near the end
  • void removeWhere(bool Function(T item) predicate) — remove matching loaded items
  • void dispose() — clean up listeners/resources

Typical getPage signature (example implementation):

// 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 totalPages = body['totalPages'] as int;
  return (items: items, totalPages: totalPages);
}

Using RestPaginator in a Flutter widget

final paginator = RestPaginator<Map<String, dynamic>>(
  getPage: fetchUsersPage,
  pageSize: 20,
);

// ListView with infinite scroll
ListView.builder(
  itemCount: paginator.availableItemCount + (paginator.endReached ? 0 : 1),
  itemBuilder: (context, index) {
    if (index >= paginator.availableItemCount) {
      return const Center(child: CircularProgressIndicator());
    }

    // getItem automatically requests the next page when near the end
    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 getPage resilient: handle network errors and return an empty RestPageData or rethrow for the paginator to handle.
  • Use getItem(index) from the UI so the paginator can automatically load upcoming pages.
  • 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.warning(), and 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'); // appears red
Log.info({'event': 'Loading user', 'id': 1});
Log.warning('Deprecated API used');
Log.trace('Deprecated API used');


6. CustomToast

A lightweight, highly customizable Flutter package for displaying animated floating toast notifications with support for Success, Error, and Warning states.

⚙️ Initialization: To use CustomToast anywhere in your app without needing BuildContext, initialize it with the same navigatorKey used by your MaterialApp.

final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

// optional
final myConfig = CustomToastConfig(
  backgroundColors: {
    ToastType.success: Colors.blueAccent,
    ToastType.error: Colors.deepPurple,
    ToastType.warning: Colors.amber,
  },
  textStyles: {
    ToastType.success: TextStyle(color: Colors.white, fontWeight: FontWeight.w600),
    ToastType.error: TextStyle(color: Colors.white, fontWeight: FontWeight.w600),
    ToastType.warning: TextStyle(color: Colors.black, fontWeight: FontWeight.w600),
  },
  borderRadius: BorderRadius.circular(20),
  margin: const EdgeInsets.all(16),
  duration: const Duration(seconds: 2),
  position: ToastPosition.top,
);

void main() {
  CustomToast.init(
    navigatorKey: navigatorKey,
    config: myConfig,
  );

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: navigatorKey, // <--- Crucial step
      home: const HomeScreen(),
    );
  }
}

Usage: Trigger an animated 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.

- Animated Overlay UI: Toasts slide and fade in from the configured top or bottom position.

- Three Built-in States: Standardized support for success, error, and warning.

- Global Theming: Easily override default colors, text styles, radius, margin, duration, and position.

- Multiple Toast Handling: Keeps a small stack of active messages and automatically removes old toasts.

7. Navigation Helper

Simplify your routing logic with the Navigate utility class. No more boilerplate MaterialPageRoute required for common navigation actions.

Features:

  • Navigate.push: Standard navigation to a new screen with optional slide transition direction.

  • Navigate.pushReplacement: Replace the current screen (e.g., after login).

  • Navigate.pushAndRemoveUntil: Clear the stack and go to a new screen (e.g., after logout).

  • Navigate.pop: Go back with an optional typed result.

// Navigate to a new page with the default right-to-left slide transition
Navigate.push(context, const ProfileScreen());

// Navigate with a custom slide direction and duration
Navigate.push(
  context,
  const DetailsScreen(),
  direction: NavDirection.bottomToUp,
  duration: const Duration(milliseconds: 400),
);

// 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());

// Go back
Navigate.pop(context);

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.$1 != null) {
    Log.success('Loaded user: ${attempt.$1}');
  } else {
    Log.error('Failure: ${attempt.$2}');
  }

  // Paginator (simplified)
  final paginator = RestPaginator<Map<String, dynamic>>(
    getPage: (page, pageSize) async {
      // Replace with real network call; return (items: items, totalPages: totalPages)
      return (items: List.generate(5, (i) => {'index': i}), totalPages: 20);
    },
  );
  await paginator.paginate(1);
  Log.info('Loaded ${paginator.availableItemCount} items');

  // Log utilities
  Log.info({'spacing': spacing});
}

Made with ❤️ for Flutter developers by Hasibul Hasan Tushar


Libraries

utills