cool_ext

A comprehensive Flutter package with powerful extensions, utility classes, and widgets to make your Flutter development easier and more productive.

Company powering the development and continuous maintenance of this package is https://www.techsukras.com

Features

This package provides lots of small utility classes, methods, and properties for day-to-day Flutter coding. It includes:

  • 🔧 Extensions for BuildContext, String, Widget, num, Iterable, MediaQueryData, and more
  • 🛠️ Utility Classes for colors, dates, type conversions, snackbars, and general helpers
  • 🎨 Pre-built Widgets like LoadingButton, ChoiceChips, MenuBoxes, and more
  • 📅 Date Formatting utilities with common format patterns

This is an opinionated package with utilities behaving in their own style. Many methods offer customization options.

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  cool_ext: ^2.23.1

Then run:

flutter pub get

Import

import 'package:cool_ext/cool_ext.dart';

📱 Extensions

BuildContext Extensions

Convenient shortcuts for common BuildContext operations:

// Instead of MediaQuery.of(context)
context.mediaQuery

// Instead of Theme.of(context)
context.theme

// Instead of Navigator.of(context)
context.navigator

// Instead of ScaffoldMessenger.of(context)
context.scaffoldMessenger

String Extensions

Nullable String Extensions:

String? text = "hello";
text.hasValue          // true if not null and not empty
text.isNullOrEmpty     // true if null or empty
text.mapIfEmpty("default")  // returns "default" if null/empty

String Extensions:

String text = "Hello World";
text.takeLeft(5)                    // "Hello"
text.takeLeft(5, addEllipses: true) // "Hello..."
text.takeAfter("Hello ")            // "World"
text.takeAfterLast("/")             // Gets text after last occurrence

// Validation
"12345".containsOnlyNumbers                    // true
"+1234567890".startsWithPlusAndContainsOnlyNumbers  // true
"red".isAnyOf(["red", "blue", "green"])       // true

Widget Extensions

Powerful widget extension methods for padding, layout, and decoration:

Padding:

Widget child = Text("Hello");
child.paddingAll(10)
child.paddingHorizontal(20)
child.paddingVertical(15)
child.paddingTop(10)
child.paddingBottom(10)
child.paddingLeft(10)
child.paddingRight(10)
child.paddingTLBR(10, 5, 10, 5)  // top, left, bottom, right
child.paddingVH(10, 20)          // vertical, horizontal
child.paddingCustom(EdgeInsets.only(top: 5))

Layout:

child.expanded()           // Wraps in Expanded
child.expanded(flex: 2)    // With flex parameter
child.flexible()           // Wraps in Flexible
child.onCenter()           // Wraps in Center
child.onScroll()           // Wraps in SingleChildScrollView
child.onSafeArea()         // Wraps in SafeArea

Decoration:

child.onWhiteContainer()   // White background
child.onRoundedWhiteContainer(bgColor: Colors.blue)
child.border(context, radius: 5, borderColor: Colors.grey)
child.tooltip("Tooltip text")
child.tooltipOnTap("Click me")

num Extensions

double value = 123.456;
value.asTwoDecimals  // "123.46" (formatted with 2 decimal places)

Iterable Extensions

Nullable Iterable Extensions:

List<int>? numbers = [1, 2, 3];
numbers.hasValue       // true if not null and not empty
numbers.isNullOrEmpty  // true if null or empty

// Map to list
numbers.mapToList((e) => e * 2)  // [2, 4, 6]
numbers.mapToNullableList((e) => e.toString())  // ["1", "2", "3"] or null

// Group by key
var items = [Item(type: "A"), Item(type: "B"), Item(type: "A")];
items.group((item) => item.type)  // {A: [item1, item3], B: [item2]}

// Convert to map
items.toMap((item) => item.id)  // {id1: item1, id2: item2}

Widget List Extensions:

List<Widget> widgets = [Text("A"), Text("B"), Text("C")];
widgets.joinWith(Divider())  // Inserts divider between each widget

MediaQueryData Extensions

MediaQueryData mq = MediaQuery.of(context);
mq.isLandscape         // true if width > height
mq.isPortrait          // true if height >= width
mq.shorterSize         // Minimum of width and height
mq.largerSize          // Maximum of width and height
mq.halfScreenWidth     // width / 2
mq.halfScreenHeight    // height / 2
// Navigate with widget directly
context.navigator.pushWidget(NextScreen())
context.navigator.pushReplacementWidget(NextScreen())
context.navigator.popUntilFirst()
context.navigator.removeUntilFirstAndPushReplacementWidget(NextScreen())

// Configure animation style
NavUtil.pushRouteAnimation = PushRouteAnimation.rightToLeft;

Icon Extensions

Icon icon = Icon(Icons.home);
icon.withSize(24.0)
icon.withColor(Colors.blue)

Dynamic Extensions

dynamic value = someNullableValue;
value.mapIfNotNull<int, String>(
  (input) => input.toString(),
  defaultValue: "N/A"
)

ScaffoldMessengerState Extensions

context.scaffoldMessenger.coolSuccessSnackBar("Success!");
context.scaffoldMessenger.coolFailureSnackBar("Error occurred");

Int Extensions (Date Formatting)

Convert milliseconds to formatted date strings:

int timestamp = DateTime.now().millisecondsSinceEpoch;
timestamp.asDDMMMYHHMMSSAAA    // "02-Nov-2025 14:30:45:123"
timestamp.asDDMMMYHHMMSS       // "02-Nov-2025 14:30:45"
timestamp.asHHMMSS             // "14:30:45"
timestamp.asHHMMA              // "02:30 PM"
timestamp.asDDMMYYYY           // "02-Nov-2025"
timestamp.asDDMMM              // "02-Nov"
timestamp.asDDMMYYYYHHMMA      // "02-Nov-2025 2:30 PM"
timestamp.asYYYYMMM            // "2025-Nov"
timestamp.asYYYYMM             // "2025-11"
timestamp.asEEEhhmma           // "Sat 02:30 PM"
timestamp.asEEE                // "Sat"

🛠️ Utility Classes

CoolUtil

A comprehensive utility class with many helper methods:

UI Helpers:

// Pre-configured input border
CoolUtil.outlinedRounded25rNoneBorder

// Network error page
CoolUtil.networkErrorPage(errorMsg: "Failed to load", icon: Icon(Icons.error))

// Text widgets
CoolUtil.boldText(context, "Bold Text")
CoolUtil.disabledText(context, "Disabled")
CoolUtil.captionText(context, "Caption", tooltip: "Info")
CoolUtil.centerText("Centered")

// Text on colored background
CoolUtil.textOnColor(context,
  text: "Label",
  backgroundColor: Colors.blue,
  textColor: Colors.white
)

// Widget on colored background
CoolUtil.widgetOnColor(context,
  widget: Text("Content"),
  backgroundColor: Colors.grey.shade200
)

// Inkwell card
CoolUtil.onInkCard(
  onTap: () {},
  child: Text("Tap me"),
  radius: 10,
  elevation: 2
)

// Clipboard helpers
CoolUtil.clipboardCopyButton(context, "Text to copy")
CoolUtil.clipboardCopyableText(context, "Text with copy button")

List Builders:

// Build and join widgets with interleaved widget
CoolUtil.buildAndJoinWidgets(
  items: ["A", "B", "C"],
  widgetBuilder: (item) => Text(item),
  interleaved: Divider(),
  startWithInterleaver: true,
  endWithInterleaver: false
)

// Build with dividers
CoolUtil.buildAndJoinDivider(
  items: myList,
  widgetBuilder: (item) => ListTile(title: Text(item))
)

Dialogs:

// Input dialog
String? result = await CoolUtil.inputDialog(
  context: context,
  title: "Enter Name",
  defaultValue: "John",
  hintText: "Type your name",
  validator: (value) => value?.isEmpty == true ? "Required" : null
);

// Confirmation dialog
CoolUtil.wrapInYesCancelConfirmDialog(
  context,
  "Delete Item",
  contentString: "Are you sure?",
  action1Text: "Delete",
  action1: () => deleteItem(),
  action1ButtonFgColor: Colors.red
);

Form Helpers:

CoolUtil.wrapInFormField(
  validator: (value) => value == null ? "Required" : null,
  child: MyCustomWidget(),
  labelText: "Field Label",
  filled: true
)

Navigation Routes:

CoolUtil.defaultRoute(NextScreen())      // Material page route
CoolUtil.animatedRoute(NextScreen())     // Animated slide transition

Converters:

CoolUtil.yesNoToBool("Yes")        // true
CoolUtil.boolToYesNo(true)         // "Yes"

Theme Size Helpers:

CoolUtil.bodyLargeSize(context)
CoolUtil.headlineLargeSize(context)
CoolUtil.displayMediumSize(context)

DateUtil

Comprehensive date formatting and manipulation utilities:

Pre-defined Formats:

DateUtil.hh24mmss                  // 24-hour format: "14:30:45"
DateUtil.hh12mma                   // 12-hour format: "02:30 PM"
DateUtil.ddmmyyyy                  // "02-Nov-2025"
DateUtil.ddmmmyyyy                 // "02-Nov-2025"
DateUtil.yyyymmm                   // "2025-Nov"
DateUtil.ddmmyhhmmssaaa           // "02-Nov-2025 14:30:45"
DateUtil.yyyymmddhhmmssForFileName // "2025-11-02-14-30-45"
DateUtil.eeehhmma                  // "Sat 02:30 PM"
DateUtil.eee                       // "Sat"

Format Methods:

// Format TimeOfDay
TimeOfDay time = TimeOfDay(hour: 14, minute: 30);
DateUtil.formatTimeOfDay(time)  // "02:30 PM"
DateUtil.formatTimeOfDay(time, formatTo12Hr: false)  // "14:30"

// Format timestamp
DateUtil.formatDateDDMMMYHHMMSS(timestamp)
DateUtil.formatDateFriendlyDateAndTimeUptoMillis(timestamp)

Parsing:

TimeOfDay time = DateUtil.parseHHMMToTimeOfDay("14:30");
TimeOfDay time2 = DateUtil.parseHHColonMMToTimeOfDay("14:30");

Date Manipulation:

DateTime now = DateTime.now();
DateUtil.addDaysAndSetHHMM(now, 5, "09:00")      // Add 5 days, set to 9 AM
DateUtil.reduceDaysAndSetHHMM(now, 2, "18:00")   // Subtract 2 days, set to 6 PM

Duration Formatting:

Duration duration = Duration(hours: 2, minutes: 30, seconds: 45);
DateUtil.getElapsedStr(duration)  // "2hr 30m 45s"
DateUtil.getElapsedStr(duration, showMilliSeconds: true)  // "2hr 30m 45s 500ms"

ColorUtil

Color color = ColorUtil.colorFromHex("#FF5733");
Color color2 = ColorUtil.colorFromHex("FF5733");  // Works without #

SnackBarUtil

Easy snackbar display with colored backgrounds:

// With context
SnackBarUtil.showSuccess(context, "Operation successful!");
SnackBarUtil.showError(context, "Something went wrong");

// With ScaffoldMessengerState
SnackBarUtil.showSuccessWithMessenger(scaffoldMessenger, "Success!");
SnackBarUtil.showFailureWithMessenger(scaffoldMessenger, "Failed");

// Customize colors
SnackBarUtil.successColor = Colors.green.shade900;
SnackBarUtil.failureColor = Colors.red.shade900;

TypeConversionUtil

// Convert dynamic list to typed lists
List<dynamic> dynamicList = [1, 2, 3];
List<String> strings = TypeConversionUtil.dynamicListToStringList(dynamicList);
List<int> ints = TypeConversionUtil.dynamicListToIntList(dynamicList);
Set<String> stringSet = TypeConversionUtil.dynamicListToStringSet(dynamicList);

DevConsole

Debug printing utilities:

DevConsole.printIfDebug("Debug message");       // Only in debug mode
DevConsole.printAlways("Always printed");        // Always prints
DevConsole.printCaught("Error occurred", error, stackTrace);  // For caught exceptions

GeneralUtil Classes

StringIdText - Represents an item with ID and text:

var item = StringIdText("id1", "Display Text");
item.toJson();
StringIdText.fromJson(json);
StringIdText.listFrom(jsonList);
StringIdText.findById(items, "id1");
StringIdText.findAllById(items, ["id1", "id2"]);

TypeIdText - Item with type, ID, and text:

var item = TypeIdText("user", "123", "John Doe");
item.toJson();

Result - Simple success/failure result:

var result = Result.successMsg("Operation completed");
var result2 = Result.failureMsg("Operation failed");

ProcessResult - Result with optional object:

var result = ProcessResult<User>.successMsg("User loaded");
var result2 = ProcessResult.failureMsg("Failed to load");

OneTimeWorker - Execute code only once:

OneTimeWorker.work(
  jobName: "initializeApp",
  action: () => print("This runs only once")
);

MsgDisplay - Widget to display ProcessResult messages:

ValueNotifier<ProcessResult<User>> notifier = ValueNotifier(ProcessResult.empty());
MsgDisplay(valueListenable: notifier)

🎨 Widgets

LoadingButton

A button that shows a loading indicator while executing an async operation:

LoadingButton(
  buttonType: LoadingButtonType.elevatedButton,
  onPressed: () async {
    await Future.delayed(Duration(seconds: 2));
  },
  child: Text("Submit"),
  tooltip: "Click to submit",
  style: ElevatedButton.styleFrom(backgroundColor: Colors.blue)
)

// Available button types:
// LoadingButtonType.elevatedButton
// LoadingButtonType.textButton
// LoadingButtonType.iconButton
// LoadingButtonType.filledButton

ChoiceChips

Single or multi-select chip widget:

// Single select
ChoiceChips(
  items: [
    StringIdText("1", "Option 1"),
    StringIdText("2", "Option 2"),
    StringIdText("3", "Option 3"),
  ],
  onSelectionChanged: (selected) {
    print("Selected: ${selected.map((e) => e.text).join(', ')}");
  },
  selectedColor: Colors.blue,
  selectedFgColor: Colors.white,
  showCheckmark: true
)

// Multi-select
ChoiceChips(
  items: items,
  multiSelect: true,
  initialSelection: [items[0], items[1]],
  onSelectionChanged: (selected) => handleSelection(selected)
)

MultiSelectChips

Convenience wrapper for multi-select chips:

MultiSelectChipsController controller = MultiSelectChipsController();

MultiSelectChips(
  items: chipItems,
  initialSelection: selectedItems,
  controller: controller
)

// Get current selection
List<StringIdText> selected = controller.currentSelections();

CoolList

Enhanced ListView with dividers, reordering, and empty state:

CoolList<User>(
  items: users,
  widgetBuilder: (user) => ListTile(title: Text(user.name)),
  msgOnEmpty: "No users found",
  title: "User List",
  addDivider: true,
  dividerColor: Colors.grey,
  shrinkWrap: true,
  listReOrderHelper: ListReOrderHelper(
    keyProvider: (user) => user.id,
    onReOrder: (oldIndex, newIndex, reorderedList) {
      // Handle reorder
    }
  )
)

Grid menu with icon boxes:

MenuBoxes(
  minCrossAxisCount: 2,
  menus: [
    MenuBoxItem(
      icon: Icon(Icons.home, size: 40),
      labelText: "Home",
      bgColor: Colors.white,
      onPressed: () => navigateToHome()
    ),
    MenuBoxItem(
      icon: Icon(Icons.settings, size: 40),
      labelText: "Settings",
      bgColor: Colors.blue.shade50,
      onPressed: () => navigateToSettings()
    ),
  ]
)

ChipFilterableListView

ListView with filtering chips:

ChipFilterableListView(
  filters: [
    StringIdText("active", "Active"),
    StringIdText("inactive", "Inactive"),
  ],
  items: [
    ChipFilterableListItem(
      {"active"},
      UserTile(user: user1)
    ),
    ChipFilterableListItem(
      {"inactive"},
      UserTile(user: user2)
    ),
  ],
  allFilter: true,
  allFilterText: "All Users",
  addDivider: true
)

OrientationLocker

Lock screen orientation for a specific screen:

OrientationLocker(
  permittedOrientations: [
    DeviceOrientation.landscapeLeft,
    DeviceOrientation.landscapeRight,
  ],
  child: VideoPlayerScreen()
)

// When widget is disposed, orientation is reset to default

Blur

Apply blur effect to any widget:

Blur(
  child: Image.network("https://example.com/image.jpg")
)

📋 Example Usage

import 'package:cool_ext/cool_ext.dart';
import 'package:flutter/material.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("cool_ext Demo")),
        body: Column(
          children: [
            // String extensions
            Text("Hello World".takeLeft(5)),

            // Widget extensions
            Text("Padded Text")
              .paddingAll(20)
              .onRoundedWhiteContainer()
              .tooltip("This is a tooltip"),

            // LoadingButton
            LoadingButton(
              buttonType: LoadingButtonType.elevatedButton,
              onPressed: () async {
                await Future.delayed(Duration(seconds: 2));
                SnackBarUtil.showSuccess(context, "Done!");
              },
              child: Text("Click Me")
            ),

            // Date formatting
            Text(DateTime.now().millisecondsSinceEpoch.asDDMMYYYY),

            // Choice chips
            ChoiceChips(
              items: [
                StringIdText("1", "Option 1"),
                StringIdText("2", "Option 2"),
              ],
              onSelectionChanged: (selected) {
                print("Selected: $selected");
              }
            ),
          ].joinWith(SizedBox(height: 10))
        ).paddingAll(16).onScroll()
      ),
    );
  }
}

🤝 Contributing

This is an opinionated package with utilities behaving in their own style. Many methods offer customization, but some may not.

If you find issues or want new features:

  • Create an issue at GitLab Issues
  • Submit a merge request with your improvements

📄 License

Please check the repository for license information.



📝 Changelog

See CHANGELOG.md for version history.