A comprehensive Flutter extension and utility package that supercharges your development workflow. Provides 16 type extensions and built-in utilities for context access, string operations, navigation, responsive sizing, and more -- all accessible through a clean .ext syntax.
Try it live at vb10.github.io/kartal — an
interactive gallery where you can type into the validators, drag the
formatters, pick colours, and resize the window to watch the responsive
breakpoints switch. Its source is example/, so every snippet in
this README is running code.
if (context.device.isSmallScreen) {
// compact layout
}
The four bands are contiguous, so exactly one is always true.
Property
Return Type
Description
isSmallScreen
bool
width < 300
isMediumScreen
bool
300 <= width < 600
isExpandedScreen
bool
600 <= width < 900
isLargeScreen
bool
width >= 900
breakpoint
DeviceBreakpoint
The band as an exhaustive enum
responsive<T>(...)
T
Picks a value for the current band
isAndroidDevice
bool
Running on Android
isIOSDevice
bool
Running on iOS
isWindowsDevice
bool
Running on Windows
isLinuxDevice
bool
Running on Linux
isMacOSDevice
bool
Running on macOS
responsive takes a required small value and optional wider ones. Wider
bands fall back to the next narrower value supplied, so you only specify the
breakpoints you actually care about:
final columns = context.device.responsive(small: 1, medium: 2, large: 4);
final padding = context.device.responsive<EdgeInsets>(
small: constEdgeInsets.all(8),
large: constEdgeInsets.all(32),
);
Switch over breakpoint when you need every case handled:
Managers are held per NavigatorState, so a loader shown before a route change
does not strand the next hideLoader on a disposed navigator. hideLoader is a
no-op when nothing is showing, making it safe to call from a finally.
Overlay
Transient UI via context.overlay.
context.overlay.showSnack('Saved');
final confirmed = await context.overlay.showConfirm(
title: 'Delete this item?',
message: 'This cannot be undone.',
confirmLabel: 'Delete',
isDestructive: true,
);
if (confirmed) awaitdelete();
await context.overlay.showSheet(constFilterSheet());
showConfirm completes with false rather than null when dismissed, so the
result is usable directly in an if. showSheet defaults to
isScrollControlled: true, since a sheet holding a text field or a long list
needs it to size correctly above the keyboard.
String Extension
Access string utilities via 'value'.ext. Works on both String and String?.
isValidEmail is more permissive than it looks. Its regex is not anchored
at the end, so trailing content passes, and it accepts a comma in the local
part. 'user@domain.com<script>' and 'veli,test@kartal.dev' both return
true. Do not use it as a sanitising gate. Tracked in
#93; the fix is deferred because it
rejects addresses that pass today.
isValidTckn, isValidIban and isValidCreditCard run the real checksum
algorithms rather than length or shape checks, so they reject plausible-looking
invalid input. Spaces, dashes and grouping are tolerated:
'4242424242424242'.ext.mask(); // '4242********4242''veli@kartal.dev'.ext.maskEmail; // 've**@kartal.dev''Cok Guzel Bir Baslik'.ext.toSlug(); // 'cok-guzel-bir-baslik''Kartal makes Flutter nicer'.ext.truncate(12); // 'Kartal ma...''Veli Bacik'.ext.initials(); // 'VB''<p>Tom & Jerry</p>'.ext.removeHtmlTags; // 'Tom & Jerry'
Property / Method
Return Type
Description
mask({start, end, char})
String
Middle masked, ends visible
maskEmail
String
Local part masked, domain kept
maskPhone({int visibleDigits})
String
Only the trailing digits kept
toSlug({String separator})
String
URL-friendly slug
truncate(int, {String ellipsis})
String
Shortened, never exceeding the budget
initials({int count})
String
Initials from the first n words
removeHtmlTags
String
Tags stripped and entities decoded
wordCount
int
Whitespace separated word count
readingTimeMinutes
int
Estimated reading time
reversed
String
Characters reversed
base64Encoded
String
Encoded as base64
base64Decoded
String?
Decoded, null when invalid
toDateTimeOrNull()
DateTime?
Parsed, null when invalid
toSlug folds diacritics before slugifying, so Turkish text survives the
conversion. truncate counts the ellipsis inside maxLength rather than
appending past it, and mask masks in full when the input is too short to keep
both ends visible, so it never leaks more than requested.
Color & Images
'FF5733'.ext.color // Color(0xffFF5733)'FF5733'.ext.toColor // Color from color code'avatar'.ext.randomImage // picsum.photos URL
errorBuilder takes precedence over onError and, unlike it, receives the
error itself.
Retrying lives in a top-level function rather than on the extension, because a
Future can only be awaited once — retrying needs something that produces a
fresh future per attempt:
averageBy returns null rather than NaN for an empty collection, so "no
data" stays distinguishable from a genuine zero average.
Iterable Extension
Note: The null-stripping helpers use the .exts (plural) accessor,
because their element type is T?. The
collection helpers are on .ext and work on any
Iterable.
Access render information and scroll behavior for GlobalKey.
final key = GlobalKey();
// after build:final widgetHeight = key.ext.height;
final position = key.ext.offset;
key.ext.scrollToWidget();
Property / Method
Return Type
Description
rendererBox
RenderBox?
RenderBox of the widget
offset
Offset?
Global position
height
double?
Widget height
scrollToWidget({ScrollPositionAlignmentPolicy})
void
Scroll to make widget visible
Int Extension
final color = 42.ext.randomColorValue; // Random 0-255final status = 200.ext.httpStatus; // HttpResult.successfinal statusColor = 404.ext.httpStatusColor; // Colors.orange
Property
Return Type
Description
randomColorValue
int
Random color value 0-255 seeded by the int
httpStatus
HttpResult
HTTP result category (success, redirection, clientError, serverError, unknown)
httpStatusColor
Color
Color for the HTTP status (green, blue, orange, red, grey)
ordinal
String
English ordinal, handling the 11th/12th/13th exception
constColor(0xFF3F51B5).ext.toHex(); // '#3f51b5'Colors.indigo.ext.lighten(0.2);
Colors.indigo.ext.contrastText(); // white or blackColors.indigo.ext.toMaterialColor(); // full 50..900 swatchKartalColor.tryParse('#FF0000'); // Color? -- null when invalidKartalColor.random(seed: 42); // deterministic
Method
Return Type
Description
toHex({includeAlpha, uppercase})
String
Hex string, #AARRGGBB when alpha is included
withOpacity(double)
Color
Alpha applied via withValues
luminance
double
WCAG relative luminance
isDark / isLight
bool
Perceptual, not a naive RGB average
contrastText({light, dark})
Color
A readable foreground for this colour
lighten([double]) / darken([double])
Color
HSL shade, clamped at white/black
blend(Color, [double t])
Color
Interpolate towards another colour
toMaterialColor()
MaterialColor
Swatch from this colour as shade 500
randomColor
MaterialColor
Random entry from Colors.primaries
isDark uses relative luminance, so perceptually bright hues such as yellow are
correctly reported as light.
KartalColor.tryParse accepts an optional leading # and either 6-digit RGB or
8-digit ARGB. String.ext.toColorOrNull is the same parser on a string
receiver, and String.ext.toColor falls back to white.
Bool Extension
Works on bool? (nullable booleans). Null is treated as failure.
Fetch Open Graph metadata (title, description, image) from any URL.
final preview = awaitCustomLinkPreview.getLinkPreviewData('https://example.com');
print(preview?.title);
print(preview?.description);
print(preview?.image);
CustomLogger
Debug-mode-only error logging.
CustomLogger.showError<MyClass>(errorObject);
Debouncer and Throttler
Debouncer waits until the caller stops firing, keeping only the last call in a
burst -- the usual case for a search field. Throttler runs the first call and
drops the rest inside the window, for continuous events such as scrolling.
Run unless inside the cooldown; returns whether it ran
Throttler.isThrottled
bool
Whether a call now would be dropped
Throttler.reset()
void
Clear the cooldown
Always dispose() both, otherwise a pending timer can outlive the widget that
owns it.
DeviceUtility
Singleton for device-related operations.
final deviceId = awaitDeviceUtility.instance.getUniqueDeviceId();
final isIpad = awaitDeviceUtility.instance.isIpad();
Configuration
Breakpoints and relative-time labels are the two opinionated parts of the
package. Both previously had values baked in with no way to change them, which
forced apps on a different design system or language to avoid those helpers
entirely. Configure them once during startup:
Everything has a default, so configuring is optional. KartalBreakpoints
asserts its values increase, which catches a misordered configuration at
construction rather than producing silently overlapping bands.