cool_ext 3.5.0
cool_ext: ^3.5.0 copied to clipboard
Cool Flutter Package with cool Utilities and Extensions for Easy and Pretty Coding
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
Navigator Extensions #
// 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
}
)
)
MenuBoxes #
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.
🔗 Links #
- Homepage: https://gitlab.com/adithyank/cool_ext
- Repository: https://gitlab.com/adithyank/cool_ext
- Issue Tracker: https://gitlab.com/adithyank/cool_ext/-/issues
📝 Changelog #
See CHANGELOG.md for version history.