Extensions that just make sense.
A handy collection of Dart and Flutter extension methods to supercharge your development experience — clean syntax, reusable logic, and expressive code, all in one lightweight package.
Designed to save you time, reduce boilerplate, and improve readability across widgets, strings, numbers, booleans, and BuildContext.
The package can be installed using the command
flutter pub add mayr_extensionsAnd can then be imported using
import 'package:mayr_extensions/mayr_extensions.dart';-
form– Easily access the nearestFormStateusingcontext.form. -
navigator– Shorthand forNavigator.of(context). -
overlay– Access the currentOverlayStatefrom the context. -
scaffold– Retrieve the nearestScaffoldStatewithcontext.scaffold.
-
scaffoldMessenger– Quickly get theScaffoldMessengerStatefor showing snackbars and more. -
void showSnackBar(String content, {Duration? duration, SnackBarBehavior behavior = SnackBarBehavior.fixed})- Quickly show a SnackBar without manually accessing ScaffoldMessenger.
-
mediaQuery– AccessMediaQueryDatafrom context. -
platformBrightness– Get the system's brightness setting (Brightness.darkorBrightness.light). -
platformInDarkMode|platformInLightMode– Returnstruebased on the app's current brightness mode. -
widgetSize– Get the rendered size of the widget associated with the context. -
widgetHeight– Convenience getter for just the height of the widget. -
widgetWidth– Convenience getter for just the width.
-
orientation– Access the current screen orientation (portraitorlandscape). -
isLandscape/isPortrait– Easy checks for current orientation. -
widgetShortestSide– Useful for responsive layouts based on the device's shortest screen edge. -
isPhone– Returnstrueif the device is considered a phone. -
isSmallTablet,isLargeTablet– Classify tablets based on width. -
isTablet– Shortcut combining both small and large tablets. -
isDesktop– Detects larger screens, typically desktops.
choose(trueValue, falseValue)– ReturnstrueValueif the boolean is true, otherwise returnsfalseValue.toInt()– Converts the boolean to an integer (1 for true, 0 for false).toYesNo({trueString, falseString})– Converts to a string representation with customizable values.not– Returns the negation of the boolean (equivalent to!this).
// Examples
true.choose('Active', 'Inactive'); // 'Active'
false.choose('Active', 'Inactive'); // 'Inactive'
true.toInt(); // 1
false.toInt(); // 0
true.toYesNo(); // 'Yes'
false.toYesNo(trueString: 'On', falseString: 'Off'); // 'Off'
true.not; // false-
isAfternoon– Checks if the time is between 12:00 PM and 5:59 PM. -
isMorning– Checks if the time is before 12:00 PM. -
isEvening– Checks if the time is between 6:00 PM and 11:59 PM. -
isNight– Checks if the time is between midnight and 5:59 AM. -
isToday/isTomorrow/isYesterday– Quickly check the relation to the current day. -
isSameDay(DateTime other)– Returnstrueif the date is the same calendar day asother. -
isInPast/isInFuture– Check if the datetime is before or after now.
startOfDay()– Returns the start of the day (midnight) for the datetime.
-
addDays(int)/addMonths(int)/addYears(int)– Add to the datetime. -
addHours(int)/addMinutes(int)/addSeconds(int)– Add smaller units. -
subDays(int)/subMonths(int)/subYears(int)– Subtract from the datetime. -
subHours(int)/subMinutes(int)/subSeconds(int)– Subtract smaller units.
-
toAge()– Convert the date to an age in years. -
isAgeOlder(age)/isAgeYounger(age)/isAgeEqualTo(age)– Check against an age. -
isAgeBetween(min, max)– Check if the age is within a given range.
-
format(String format)– Fully custom format usingintl.Popular date and time formats included in the [MayrDateTimeFormats] class.
Currently includes:
MayrDateTimeFormats.ukDate- dd/MM/yyyyMayrDateTimeFormats.ukDateTime- dd/MM/yyyy HH:mm:ssMayrDateTimeFormats.usDate- yyyy-MM-ddMayrDateTimeFormats.usDateTime- yyyy-MM-dd HH:mm:ssMayrDateTimeFormats.time- HH:mm:ssMayrDateTimeFormats.timeNoSecs- HH:mm
-
toDayOrdinal()– Get the day of the month with ordinal (e.g.1st,22nd,31st). -
toTimeAgoString()– Human-readable "time ago" format (e.g. "2 days ago"). -
toTimeString()– Convert to time only (e.g.14:35or14:35:59). -
toShortDate()– Returns a short formatted date string (e.g.Wed 15th Jan).
delay([callback])– Delays execution for the given duration. Optionally accepts a callback to run after the delay.toReadableString()– Returns a human-readable string representation (e.g., '2h 30m', '1d 5h 30m').isLongerThan(other)– Checks if this duration is longer than another duration.isShorterThan(other)– Checks if this duration is shorter than another duration.
// Example
await 2.seconds.delay(() {
print('Delayed by 2 seconds');
});
final duration = Duration(hours: 2, minutes: 30);
print(duration.toReadableString()); // '2h 30m'
5.seconds.isLongerThan(3.seconds); // true
3.seconds.isShorterThan(5.seconds); // truelet(transform)– Executes a function with this object as its argument and returns the result. Useful for chaining operations or transforming values inline.also(action)– Executes a function with this object and returns this object. Useful for performing side effects while maintaining the original value for further chaining.
// Examples
final result = 'hello'.let((it) => it.toUpperCase()); // 'HELLO'
final length = 'test'.let((it) => it.length); // 4
final user = User('John')
.also((it) => print('Created user: ${it.name}'))
.also((it) => log.info('User created'));-
nullOnDebug<T>()– Returnsnullonly in debug mode; retains value in release/profile. Useful for testing nullable flows. -
onlyOnDebug<T>()– Returns the value only in debug mode, otherwisenull. -
maybe<T>({double probability = 0.5})– Randomly returnsnullbased on the given probability (between 0.0 and 1.0). Great for simulating unreliable data in tests or dev mode.final value = 'Simulate me'.maybe(probability: 0.3); // Has a 30% chance of being null
-
orDefault(T fallback)- Returns the fallback value if the provided value is null
circleAvatar({ ... })– Quickly convert anImageProviderto aCircleAvatarwidget with full customisation options.
// Example
NetworkImage('https://example.com/pic.jpg').circleAvatar(radius: 40);- backgroundColor – Background colour of the avatar (default is transparent).
- radius – Sets the circular radius of the avatar.
- minRadius / maxRadius – Optional constraints.
- foregroundColor – Colour for the foreground image.
- onBackgroundImageError / onForegroundImageError – Handle image load failures.
-
isEqual(otherNum)– Checks if two numbers are exactly equal. -
isGreaterThan(otherNum)– Returnstrueif the number is greater. -
isLessThan(otherNum)– Returnstrueif the number is less. -
clampMin(min)– Clamps the number to a minimum value. -
clampMax(max)– Clamps the number to a maximum value. -
isBetween(min, max)– Checks if the number is within a range (inclusive). -
isPositive– Returnstrueif the number is greater than zero. -
isNegativeNumber– Returnstrueif the number is less than zero. -
isZero– Returnstrueif the number equals zero.
isEvenNumber– Checks if the integer is even.isOddNumber– Checks if the integer is odd.times(action)– Repeats an action n times.timesIndexed(action)– Repeats an action n times with the current index.
// Example
5.isBetween(1, 10); // true
3.times(() => print('Hello')); // Prints 'Hello' 3 times
3.timesIndexed((i) => print('Index: $i')); // Prints indices 0, 1, 2-
randomLess({min = 1.0})– Forintordouble, generates a random value less than the current one, starting from themin. -
randomMore(max)– Generates a random value greater than the current one, up tomax.
10.randomLess(); // e.g. returns 3, 7, etc.
5.5.randomMore(10.0); // e.g. returns 6.23, etc.toDecimalPlaces(places)– Rounds the double to a specified number of decimal places.
3.14159.toDecimalPlaces(2); // 3.14
3.14159.toDecimalPlaces(4); // 3.1416-
formatAsCurrency({locale, symbol, decimalDigits})– Formats the number as currency. -
formatAsDecimal({locale, decimalDigits})– Formats the number as a decimal with specified precision. -
formatAsNumber({locale})– Formats as a regular number string.
1234.5.formatAsCurrency(locale: 'en_NG', symbol: '₦'); // ₦1,234.50days,hours,minutes,seconds,milliseconds,microseconds– Shorthand for converting numbers to Duration.
// Example
await 2.seconds.delay(); // Waits for 2 seconds-
copyToClipboard()- Copies the string to clipboard. -
matchesRegExp(regex)– Checks if the string matches a given regular expression. -
toBool– Converts"true"or"false"to a boolean. -
toDateTime()– Parses the string into aDateTimeobject. Returns null if parse fails -
toRegExp()– Converts the string into aRegExp. -
toUri()- Attempts to parse the string to aUri -
limit(maxLength, [overflow = "…"])– Limits string length with optional overflow characters. -
mask({start = 2, end = 2, maskChar = '*', maskLength})– Masks the middle of the string, leaving edges visible.'08012345678'.mask(); // 08*******78 '08012345678'.mask(maskLength: 2); // 08**78
-
reverse()– Reverses the string. -
isBlank/isNotBlank– Checks if the string is empty or contains only whitespace. -
removeWhitespace()– Removes all whitespace from the string. -
countOccurrences(substring)– Counts how many times a substring appears. -
truncate(maxLength, {ellipsis})– Truncates the string with word boundary awareness. -
wrap(prefix, [suffix])– Wraps the string with a prefix and optional suffix. -
removePrefix(prefix)– Removes a prefix if it exists. -
removeSuffix(suffix)– Removes a suffix if it exists.
// Examples
'hello'.reverse(); // 'olleh'
' '.isBlank; // true
'hello world'.removeWhitespace(); // 'helloworld'
'hello world'.countOccurrences('l'); // 3
'The quick brown fox'.truncate(10); // 'The quick...'
'text'.wrap('"'); // '"text"'
'Hello World'.removePrefix('Hello '); // 'World'-
prettyJson()– Formats a raw JSON string. -
prettyXml()– Formats raw XML into readable indents. -
prettyYaml()– Formats YAML strings prettily.
-
camelCase– Converts string to camelCase. -
capitalised– Capitalises the first letter of each word. -
kebabCase– Converts string to kebab-case. -
pascalCase– Converts string to PascalCase. -
snakeCase– Converts string to snake_case. -
titleCase– Converts string to Title Case.
'the big brown fox'.camelCase; // theBigBrownFox
'the big brown fox'.capitalised; // The big brown fox
'the big brown fox'.pascalCase; // TheBigBrownFox
'the big brown fox'.kebabCase; // the-big-brown-fox
'the big brown fox'.snakeCase; // the_-_big_-_brown_-_fox
'the big brown fox'.titleCase; // The Big Brown Fox-
isCamelCase -
isPascalCase -
isSnakeCase -
isKebabCase -
isTitleCase -
isCapitalised -
isUpperCase -
isLowerCase
-
isEmail -
isURL -
isUlid -
isUuid -
isSlug -
isHexColor -
isIPAddress -
isNum – Validates numeric string -
isAlphabetOnly -
isNumericOnly
firstOrNull()→ Returns first element ornullif emptylastOrNull()→ Returns last element ornullif emptysingleWhereOrNull(predicate)→ Returns match ornullcontainsWhere(predicate)→ Boolean checkindexWhereOrNull(predicate)→ Returns index ornull
// Examples
[1, 2, 3].firstOrNull(); // 1
[].firstOrNull(); // null
[1, 2, 3].singleWhereOrNull((e) => e == 2); // 2
[1, 2, 3].containsWhere((e) => e > 2); // truegetOrNull(index)→ Returns element at index ornullgetOrDefault(index, defaultValue)→ Returns element or default value
// Examples
[1, 2, 3].getOrNull(1); // 2
[1, 2, 3].getOrNull(5); // null
[1, 2, 3].getOrDefault(5, 0); // 0chunked(size)→ Splits into chunksmapIndexed((index, item) => ...)→ Maps with indexwhereNotNull()→ Filters out nullsdistinctBy(keySelector)→ Unique items by propertyflatten()→ Flattens nested listssortedBy(keySelector)/sortedByDesc(keySelector)→ Sort by propertyflip()→ Reverses the list
// Examples
[1, 2, 3, 4, 5].chunked(2); // [[1, 2], [3, 4], [5]]
['a', 'b', 'c'].mapIndexed((i, e) => '$i: $e'); // ['0: a', '1: b', '2: c']
[1, null, 2, null, 3].whereNotNull(); // [1, 2, 3]
[[1, 2], [3, 4]].flatten(); // [1, 2, 3, 4]
[1, 2, 3].flip(); // [3, 2, 1]sumBy(num Function(T))→ Sum elements by selectoraverageBy(num Function(T))→ Average by selectormin()→ Minimum valuemax()→ Maximum valuecountWhere(predicate)→ Count matching elements
// Examples
[1, 2, 3, 4, 5].sumBy((e) => e); // 15
[1, 2, 3, 4, 5].averageBy((e) => e); // 3.0
[3, 1, 4, 1, 5].min(); // 1
[3, 1, 4, 1, 5].max(); // 5
[1, 2, 3, 4, 5].countWhere((e) => e > 3); // 2insertIf(condition, value)→ Insert conditionallyreplaceWhere(predicate, newValue)→ Replace matching elementsremoveWhereNot(predicate)→ Keep only matching elementsupdateWhere(predicate, updater)→ Update matching elementsaddIf(value)/addAllIf(values)→ Add conditionallyappend(value)/appendAll(values)→ Append elementsappendIf(value)/appendAllIf(values)→ Append conditionallypop()→ Remove and return last elementfliter(predicate)→ Filter elementsunique()→ Get unique elements
// Examples
[1, 2, 3].insertIf(true, 4); // [1, 2, 3, 4]
[1, 2, 3, 2].replaceWhere((e) => e == 2, 5); // [1, 5, 3, 5]
[1, 2, 3, 4, 5].removeWhereNot((e) => e > 2); // [3, 4, 5]
[1, 2, 2, 3, 3, 4].unique(); // [1, 2, 3, 4]isNullOrEmpty()→ Check if emptyjoinToString(separator, transform)→ Join with custom formatforEachIndexed()→ Iterate with index
// Examples
[].isNullOrEmpty(); // true
[1, 2, 3].joinToString(separator: ', '); // '1, 2, 3'
['a', 'b'].forEachIndexed((i, e) => print('$i: $e'));getOrNull(key)→ Get value or nullgetOrDefault(key, defaultValue)→ Get value or default
// Examples
{'a': 1, 'b': 2}.getOrNull('a'); // 1
{'a': 1, 'b': 2}.getOrNull('c'); // null
{'a': 1, 'b': 2}.getOrDefault('c', 0); // 0mapKeys((k, v) => newKey)→ Transform keysmapValues((k, v) => newValue)→ Transform valuesfilterKeys(predicate)→ Filter by keysfilterValues(predicate)→ Filter by valuesinvert()→ Swap keys and values
// Examples
{'a': 1, 'b': 2}.mapKeys((k, v) => k.toUpperCase()); // {'A': 1, 'B': 2}
{'a': 1, 'b': 2}.mapValues((k, v) => v * 2); // {'a': 2, 'b': 4}
{'a': 1, 'b': 2}.invert(); // {1: 'a', 2: 'b'}merge(otherMap)→ Merge with precedencemergeIfAbsent(otherMap)→ Merge without overridingcombine(other, (k, v1, v2) => mergedValue)→ Custom merge
// Examples
{'a': 1, 'b': 2}.merge({'b': 3, 'c': 4}); // {'a': 1, 'b': 3, 'c': 4}
{'a': 1, 'b': 2}.mergeIfAbsent({'b': 3, 'c': 4}); // {'a': 1, 'b': 2, 'c': 4}keysWhere(predicate)→ Get keys matching predicatevaluesWhere(predicate)→ Get values matching predicatetoQueryString()→ Convert to URL query string
// Examples
{'a': 1, 'b': 2, 'c': 3}.keysWhere((v) => v > 1); // ['b', 'c']
{'name': 'John', 'age': '30'}.toQueryString(); // 'name=John&age=30'toggle(element)→ Adds if missing, removes if presentintersects(otherSet)→ Check for intersectionisSubsetOf(otherSet)→ Check if subsetisSupersetOf(otherSet)→ Check if supersetunionAll(sets)→ Union of multiple setswithout(element)→ Remove element
// Examples
{1, 2, 3}.toggle(2); // {1, 3}
{1, 2, 3}.toggle(4); // {1, 2, 3, 4}
{1, 2, 3}.intersects({2, 3, 4}); // true
{1, 2}.isSubsetOf({1, 2, 3}); // true
{1, 2}.unionAll([{2, 3}, {3, 4}]); // {1, 2, 3, 4}The goal of humanize is simple:
Convert technical or numeric values into readable, natural, human-friendly strings.
Where computers speak in seconds, bytes, and counts, humanize translates them into something that sounds like it came from a person.
.humanize(locale)→ "2 hours, 3 minutes"
// Examples
Duration(hours: 2, minutes: 3).humanize(); // '2 hours, 3 minutes'
Duration(days: 1).humanize(); // '1 day'
Duration(seconds: 45).humanize(); // '45 seconds'.humanize(locale)→ "just now", "3 hours ago", "yesterday", "last week", "3 days from now", "2 weeks ago"
// Examples
DateTime.now().humanize(); // 'just now'
DateTime.now().subtract(Duration(hours: 3)).humanize(); // '3 hours ago'
DateTime.now().subtract(Duration(days: 1)).humanize(); // 'yesterday'
DateTime.now().add(Duration(days: 2)).humanize(); // 'in 2 days'humanizeNumber()→ "15.3k", "1.5M"humanizeOrdinal()→ "1st", "2nd", "3rd"humanizeCount('item')→ "1 item" / "3 items"humanizePercentage(max, min)→ "74%"humanizeFileSize()→ "1.0 MB", "520.3 KB"
// Examples
1234.humanizeNumber(); // '1.2k'
1500000.humanizeNumber(); // '1.5M'
1.humanizeOrdinal(); // '1st'
21.humanizeOrdinal(); // '21st'
3.humanizeCount('item'); // '3 items'
0.75.humanizePercentage(); // '75%'
1024.humanizeFileSize(); // '1.0 KB'
520300.humanizeFileSize(); // '508.1 KB'-
center({heightFactor, widthFactor})– Wraps widget in aCenter. -
expanded([flex = 1])– Wraps widget in anExpanded. -
flexible({flex = 1, fit = FlexFit.loose})– Wraps widget in aFlexible. -
opacity(opacity)– Wraps widget with anOpacitywidget. -
sizedBox({width, height})– Wraps widget with aSizedBox. -
constrained({maxHeight, maxWidth, minHeight, minWidth})– Wraps widget with aConstrainedBox.
-
clipRect()– Clips widget to a rectangle. -
clipRRect(borderRadius)– Clips widget with rounded corners. -
clipRounded([radius = 12])– Quickly clip widget with a uniform rounded border.
-
paddingAll(padding)– Adds equal padding on all sides. -
paddingSymmetric({horizontal, vertical})– Adds symmetric horizontal and vertical padding. -
paddingOnly({left, top, right, bottom})– Custom padding for specific sides. -
paddingZero()– Adds zero padding.
-
positionAlign(alignment)– Aligns widget usingAlign. -
positionedFill()– Fills parent constraints usingPositioned.fill.
-
hideIf(condition)– Hides widget (returnsSizedBox.shrink()) ifconditionis true. -
hideUnless(condition)– Hides widget unlessconditionis true. -
showIf(condition)– Shows widget ifconditionis true, otherwise hides. -
showUnless(condition)– Shows widget unlessconditionis true.
A helper class for managing taps on a widget in a cleaner way.
-
inkWellManager(callback, {color = Colors.transparent})– Wraps widget with anInkWellfor tap detection. -
onTap()– Wraps child withInkWellfor tap gesture. -
onDoubleTap()– Wraps child withInkWellfor double-tap gesture. -
onLongPress()– Wraps child withInkWellfor long-press gesture.
Tip: Used alongside the
inkWellManagerextension to easily attach tap interactions without boilerplate.
Text('Click Me')
.inkWellManager(() => print('Tapped'), color: Colors.black)
.onTap();Normally, to make a widget respond to taps, you must manually wrap it inside an InkWell every time, setting colours and callbacks.
InkWellManager simplifies this by providing quick .onTap(), .onDoubleTap(), and .onLongPress() methods — making your code shorter, cleaner, and more maintainable.
It also auto-applies the same splash, hover, and focus colours without extra setup.
This package also include some common date time formats. These inlude:
MayrDateTimeFormats.ukDateMayrDateTimeFormats.usDateMayrDateTimeFormats.timeMayrDateTimeFormats.timeNoSecsMayrDateTimeFormats.ukDateTimeMayrDateTimeFormats.usDateTime
To use, simply import the package into your project and you can then all of the extensions it provdes 🫶🏾
import 'package:mayr_extensions/mayr_extensions.dart';
Contributions are highly welcome! If you have ideas for new extensions, improvements, or fixes, feel free to fork the repository and submit a pull request.
Please make sure to:
- Follow the existing coding style.
- Write tests for new features.
- Update documentation if necessary.
Let's build something amazing together!
If you encounter a bug, unexpected behaviour, or have feature requests:
- Open an issue on the repository.
- Provide a clear description and steps to reproduce (if it's a bug).
- Suggest improvements if you have any ideas.
Your feedback helps make the package better for everyone!
This package is licensed under the MIT License — which means you are free to use it for commercial and non-commercial projects, with proper attribution.
See the LICENSE file for more details.
If you find this package helpful, please consider giving it a ⭐️ on GitHub — it motivates and helps the project grow!
You can also support by:
- Sharing the package with your friends, colleagues, and tech communities.
- Using it in your projects and giving feedback.
- Contributing new ideas, features, or improvements.
Every little bit of support counts! 🚀💙