Kartal은 컨텍스트, 문자열 등 기본 유형에 대한 편리한 확장을 제공하여 앱 개발을 향상시키기 위해 설계된 Dart 확장 패키지입니다. 직관적이고 재사용 가능한 메서드를 통해 일반적인 작업을 단순화하고 코드의 가독성을 높이며 반복 코드를 줄입니다. Flutter 앱 개발 중 더 빠르고 깔끔한 코드를 원하는 개발자에게 이상적이며, 여러 플랫폼에서 원활하게 통합됩니다. 경량성과 개발자 경험 중심의 접근으로 어떤 Flutter 프로젝트에도 실용적인 보완이 됩니다.
사용 사례
문자열 조작 간소화
컨텍스트 사용 강화
UI 코드 내 뼈대 코드 줄이기
코드 가독성 향상
개발 워크플로우 가속화
일반적인 유틸리티 작업 간소화
주요 기능
기본 유형 확장
컨텍스트 및 문자열 유틸리티
최소한의 보일러플레이트
크로스 플랫폼 호환성
pub를 통한 간편한 통합
개발자 중심의 디자인
적합한 대상
Flutter 초보자
경험 많은 플러터 개발자
빠른 프로토타이핑이 필요한 프로젝트
코드 명확성을 우선시하는 팀
다중 플랫폼을 대상으로 하는 앱
유용성 향상을 원하는 개발자
고려 사항
Dart 기본 유형 확장에 한정
패키지 크기가 약간 증가할 수 있습니다
외부 종속성에 의존합니다
복잡한 비즈니스 로직에 적합하지 않음
Dart 확장에 대한 이해 필요
다른 유틸리티 패키지와 함께 사용하는 것이 가장 좋습니다
원본 README
연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.
Kartal
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.