Pub Version GitHub Stars License: MIT

Kartal

A comprehensive Flutter extension and utility package that supercharges your development workflow. Provides 13 type extensions and built-in utilities for context access, string operations, navigation, responsive sizing, and more -- all accessible through a clean .ext syntax.

Table of Contents

Installation

Add kartal to your pubspec.yaml:

dependencies:
  kartal: 4.3.0-dev.1

Then run:

flutter pub get

Import it in your Dart files:

import 'package:kartal/kartal.dart';

Platform Support

Android iOS Web macOS Windows Linux

Requirements: Dart >=3.3.1 | Flutter >=3.19.0

Quick Start

// Responsive sizing and theming
Container(
  padding: context.padding.low,
  height: context.sized.dynamicHeight(0.1),
  child: Text(
    'Hello',
    style: context.general.textTheme.titleMedium,
  ),
)

// Form validation
final isValid = 'user@mail.com'.ext.isValidEmail; // true

// Conditional visibility
const Text('Premium Feature').ext.toVisible(value: isPremiumUser)

// Safe future building
fetchUserData().ext.toBuild(
  onSuccess: (data) => Text(data?.name ?? ''),
  loadingWidget: const CircularProgressIndicator(),
  notFoundWidget: const Text('No data'),
  onError: const Text('Something went wrong'),
)

Extensions

Context Extensions

Kartal extends BuildContext with 7 sub-extensions: context.general, context.sized, context.padding, context.border, context.device, context.route, and context.popupManager.

General

Access theme data, media query, keyboard state, and focus management.

final theme = context.general.appTheme;
final isOpen = context.general.isKeyBoardOpen;
context.general.unfocus(); // dismiss keyboard
Property / Method Return Type Description
mediaQuery MediaQueryData Current media query data
mediaSize Size Current media size
mediaViewInset EdgeInsets Current view insets
mediaBrightness Brightness Platform brightness
mediaTextScale(double) double Scaled font size
appTheme ThemeData Current app theme
textTheme TextTheme Text theme from current theme
primaryTextTheme TextTheme Primary text theme
colorScheme ColorScheme Color scheme from current theme
randomColor MaterialColor Random material primary color
isKeyBoardOpen bool Whether the software keyboard is visible
keyboardPadding double Height of the keyboard when open
appBrightness Brightness Platform brightness (light/dark)
focusNode FocusNode Current focus scope node
unfocus() void Remove focus from current widget

Sized

Responsive sizing helpers based on device dimensions.

SizedBox(
  height: context.sized.dynamicHeight(0.1),
  width: context.sized.dynamicWidth(0.5),
)
Property / Method Return Type Description
height double Device height
width double Device width
lowValue double 1% of device height
normalValue double 2% of device height
mediumValue double 4% of device height
highValue double 10% of device height
dynamicWidth(double) double Width multiplied by value
dynamicHeight(double) double Height multiplied by value
emptySizedWidthBoxLow Widget 1% width empty box
emptySizedWidthBoxLow3x Widget 3% width empty box
emptySizedWidthBoxNormal Widget 5% width empty box
emptySizedWidthBoxHigh Widget 10% width empty box
emptySizedHeightBoxLow Widget 1% height empty box
emptySizedHeightBoxLow3x Widget 3% height empty box
emptySizedHeightBoxNormal Widget 5% height empty box
emptySizedHeightBoxHigh Widget 10% height empty box

Padding

Responsive padding helpers. Values are percentages of device height: low=1%, normal=2%, medium=4%, high=10%.

Padding(
  padding: context.padding.horizontalNormal,
  child: Text('Hello'),
)
Property Return Type Description
low EdgeInsets 1% padding on all sides
normal EdgeInsets 2% padding on all sides
medium EdgeInsets 4% padding on all sides
high EdgeInsets 10% padding on all sides
horizontalLow EdgeInsets 1% horizontal padding
horizontalNormal EdgeInsets 2% horizontal padding
horizontalMedium EdgeInsets 4% horizontal padding
horizontalHigh EdgeInsets 10% horizontal padding
verticalLow EdgeInsets 1% vertical padding
verticalNormal EdgeInsets 2% vertical padding
verticalMedium EdgeInsets 4% vertical padding
verticalHigh EdgeInsets 10% vertical padding
onlyLeftLow EdgeInsets 1% left-only padding
onlyLeftNormal EdgeInsets 2% left-only padding
onlyLeftMedium EdgeInsets 4% left-only padding
onlyLeftHigh EdgeInsets 10% left-only padding
onlyRightLow EdgeInsets 1% right-only padding
onlyRightNormal EdgeInsets 2% right-only padding
onlyRightMedium EdgeInsets 4% right-only padding
onlyRightHigh EdgeInsets 10% right-only padding
onlyBottomLow EdgeInsets 1% bottom-only padding
onlyBottomNormal EdgeInsets 2% bottom-only padding
onlyBottomMedium EdgeInsets 4% bottom-only padding
onlyBottomHigh EdgeInsets 10% bottom-only padding
onlyTopLow EdgeInsets 1% top-only padding
onlyTopNormal EdgeInsets 2% top-only padding
onlyTopMedium EdgeInsets 4% top-only padding
onlyTopHigh EdgeInsets 10% top-only padding

Border

Border radius and rounded rectangle helpers based on device width.

Container(
  decoration: BoxDecoration(
    borderRadius: context.border.normalBorderRadius,
  ),
)
Property Return Type Description
lowRadius Radius 2% of width circular radius
normalRadius Radius 5% of width circular radius
highRadius Radius 10% of width circular radius
lowBorderRadius BorderRadius 2% of width all corners
normalBorderRadius BorderRadius 5% of width all corners
highBorderRadius BorderRadius 10% of width all corners
roundedRectangleBorderLow RoundedRectangleBorder Low radius top corners
roundedRectangleAllBorderNormal RoundedRectangleBorder Normal radius all corners
roundedRectangleBorderNormal RoundedRectangleBorder Normal radius top corners
roundedRectangleBorderMedium RoundedRectangleBorder Medium radius top corners
roundedRectangleBorderHigh RoundedRectangleBorder High radius top corners

Device

Screen size checks and platform detection.

if (context.device.isSmallScreen) {
  // compact layout
}
Property Return Type Description
isSmallScreen bool 0 <= width < 300
isMediumScreen bool 300 <= width < 600
isLargeScreen bool width >= 900
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

Route navigation helpers via context.route.

context.route.pop();
context.route.navigateName('/details', data: item);
context.route.navigateToPage(const DetailsPage());
Method Return Type Description
navigation NavigatorState Current navigator state
pop<T>([T? data]) Future<bool> Pop current route
popWithRoot() void Pop to root route
navigateName<T>(String path, {Object? data}) Future<T?> Push named route
navigateToReset<T>(String path, {Object? data}) Future<T?> Push named and clear stack
navigateToPage<T>(Widget page, {Object? extra, SlideType type}) Future<T?> Push widget with slide transition

Show and hide loading dialogs via context.popupManager.

// Show loader
context.popupManager.showLoader();

// Hide loader
context.popupManager.hideLoader();

// With custom widget and ID
context.popupManager.showLoader(
  id: 'upload',
  widgetBuilder: (context) => const MyCustomLoader(),
);
context.popupManager.hideLoader(id: 'upload');
Method Return Type Description
showLoader({String? id, bool barrierDismissible, WidgetBuilder? widgetBuilder}) void Show a loading dialog
hideLoader({String? id}) void Hide loader by ID or the latest one

String Extension

Access string utilities via 'value'.ext. Works on both String and String?.

Validation & Formatting
'test@email.com'.ext.isValidEmail      // true
'Abc123!@'.ext.isValidPassword         // true
'hello world'.ext.toTitleCase()        // "Hello World"
'hello world'.ext.toCapitalized()      // "Hello world"
'ÇÖĞ'.ext.withoutSpecialCharacters    // "COG"
'ÇÖĞ test'.ext.searchable             // "cog test"
Property / Method Return Type Description
isNullOrEmpty bool true if null or empty
isNotNullOrNoEmpty bool true if not null and not empty
isValidEmail bool Email validation via regex
isValidPassword bool Min 8 chars, upper, lower, number, symbol
searchable String Lowercase with diacritics removed
withoutSpecialCharacters String? Removes diacritics
toCapitalized() String First letter uppercase, rest lowercase
toTitleCase() String Each word capitalized
lineLength int Number of lines
phoneFormatValue String Unmasked phone value
timeFormatValue String Unmasked time value
timeOverlineFormatValue String Unmasked time overline value
Color & Images
'FF5733'.ext.color          // Color(0xffFF5733)
'FF5733'.ext.toColor        // Color from color code
'avatar'.ext.randomImage    // picsum.photos URL
Property Return Type Description
color Color Color from hex string
colorCode int? Parsed color code
toColor Color Color from color code
randomImage String Random 200x300 image URL
randomSquareImage String Random 200x200 image URL
customProfileImage String Gravatar placeholder URL
customHighProfileImage String Gravatar high-res placeholder URL
Sharing & Launching
'user@mail.com'.ext.launchEmail
'+905551234567'.ext.launchPhone
'https://pub.dev'.ext.launchWebsite
'Istanbul'.ext.launchMaps()
'Hello!'.ext.share()
Property / Method Return Type Description
launchEmail Future<bool> Open email app
launchPhone Future<bool> Open phone app
launchWebsite Future<bool> Open URL in browser
launchWebsiteCustom(...) Future<bool> Open URL with custom config
launchMaps() Future<bool> Open maps (Apple Maps on iOS, Google Maps on Android)
shareWhatsApp() Future<void> Share via WhatsApp
shareMail(String title) Future<void> Share via email
share() Future<void> Share via system dialog
Platform Info & Utilities
final name = ''.ext.appName;
final id = await ''.ext.deviceId;
final header = 'my-token'.ext.bearer; // {'Authorization': 'Bearer my-token'}
Property Return Type Description
appName String Application name
packageName String Package name
version String App version
buildNumber String Build number
deviceId Future<String> Unique device ID
bearer Map<String, dynamic> Bearer token authorization header
JSON & Type Conversion
final map = await '{"name":"kartal"}'.ext.safeJsonDecodeCompute<Map<String, dynamic>>();
final intVal = '42'.ext.toPrimitiveFromGeneric<int>(); // 42
Method Return Type Description
safeJsonDecodeCompute<T>() Future<T?> JSON decode in background isolate
toPrimitiveFromGeneric<T>() T? Convert to bool, int, double, or String

Widget Extension

const Text('Hello').ext.toVisible(value: isLoggedIn)
const Text('Disabled').ext.toDisabled(opacity: 0.5)
myWidget.ext.sliver  // wrap in SliverToBoxAdapter
Method Return Type Description
toVisible({bool value = true}) Widget Show widget or SizedBox.shrink()
toDisabled({bool? disable, double? opacity}) Widget Wrap in IgnorePointer + Opacity (default opacity: 0.2)
sliver Widget Wrap in SliverToBoxAdapter

Future Extension

fetchData().ext.toBuild(
  onSuccess: (data) => Text(data.toString()),
  loadingWidget: const CircularProgressIndicator(),
  notFoundWidget: const Text('Not found'),
  onError: const Text('Error'),
)

// Returns null on timeout instead of throwing
final result = await fetchData().ext.timeoutOrNull(
  timeOutDuration: const Duration(seconds: 5),
);
Method Return Type Description
toBuild({onSuccess, loadingWidget, notFoundWidget, onError, data}) Widget FutureBuilder with typed callbacks
timeoutOrNull({Duration timeOutDuration, bool enableLogger}) Future<T?> Returns null on timeout (default: 10s)

List Extension

Works on both List<T> and List<T>?.

List<String>? names;
names.ext.isNullOrEmpty        // true (null-safe)
[1, null, 3].ext.makeSafe()    // [1, 3]
['a', 'b'].ext.indexOrNull((e) => e == 'b')  // 1
Property / Method Return Type Description
isNullOrEmpty bool true if list is null or empty
isNotNullOrEmpty bool true if list has elements
makeSafe() List<T> Filters out null values
indexOrNull(bool Function(T)) int? Index of first match, or null

Iterable Extension

Note: Uses .exts (plural) accessor.

[null, 1, null, 3].exts.makeSafe()  // [1, 3]
[1, 2, 3, null].exts.makeSafeCustom((v) => v != null && v > 1)  // [2, 3]
Method Return Type Description
makeSafe() List<T> Filters out null values
makeSafeCustom(bool Function(T?)) List<T> Filters by custom predicate

File Extension

final file = File('photo.jpg');
file.ext.isImageFile  // true
file.ext.fileType     // FileType.IMAGE
Property Return Type Description
fileType FileType File type based on MIME (IMAGE, VIDEO, AUDIO, TEXT, UNKNOWN)
isImageFile bool Check if image
isVideoFile bool Check if video
isAudioFile bool Check if audio
isTextFile bool Check if text

Image Extension

Apply rotation transformations to Image widgets.

Image.network('https://picsum.photos/200').ext.upRotation
Property Return Type Description
rightRotation Widget 180-degree rotation
upRotation Widget 90-degree (quarter turn) rotation
bottomRotation Widget 270-degree rotation
leftRotation Widget 360-degree (full) rotation

Key Extension

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-255
final status = 200.ext.httpStatus;           // HttpResult.success
final 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)

Bool Extension

Works on bool? (nullable booleans). Null is treated as failure.

bool? apiResult = true;
apiResult.ext.isSuccess  // true
apiResult.ext.isFail     // false

bool? nullResult;
nullResult.ext.isSuccess // false
nullResult.ext.isFail    // true
Property Return Type Description
isSuccess bool true only if value is true
isFail bool true if value is false or null

Date Extension

Human-readable relative time with localizable labels. Works on both DateTime and DateTime?.

final postDate = DateTime(2024, 1, 15);
postDate.ext.differenceTime()  // "1 years ago"

// Custom localization
postDate.ext.differenceTime(
  localizationLabel: DateLocalizationLabel(
    yearLabel: 'yil once',
    monthLabel: 'ay once',
    dayLabel: 'gun once',
  ),
)
Method Return Type Description
differenceTime({DateLocalizationLabel}) String Human-readable time difference from now

DateLocalizationLabel fields: yearLabel, monthLabel, dayLabel, hourLabel, minuteLabel, secondLabel (all default to English, e.g. "years ago").


Map Extension

final map = {'name': 'Kartal', 'version': 4};
final json = await map.ext.safeJsonEncodeCompute(); // runs in isolate
Method Return Type Description
safeJsonEncodeCompute() Future<String?> JSON-encode in background isolate; returns null on failure

Utilities

BundleDecoder

Parse local asset JSON files into typed Dart models using isolate-based decoding. Your model must implement IAssetModel<T> with a fromJson factory.

final posts = await BundleDecoder('assets/posts.json')
    .crackBundle<Post, List<Post>>(model: Post());

MapsUtility

Open map applications with a search query.

await MapsUtility.openAppleMapsWithQuery('Istanbul Kartal');
await MapsUtility.openGoogleMapsWithQuery('Istanbul Kartal');
await MapsUtility.openGoogleWebMapsWithQuery('Istanbul Kartal');

CustomLinkPreview

Fetch Open Graph metadata (title, description, image) from any URL.

final preview = await CustomLinkPreview.getLinkPreviewData('https://example.com');
print(preview?.title);
print(preview?.description);
print(preview?.image);

CustomLogger

Debug-mode-only error logging.

CustomLogger.showError<MyClass>(errorObject);

DeviceUtility

Singleton for device-related operations.

final deviceId = await DeviceUtility.instance.getUniqueDeviceId();
final isIpad = await DeviceUtility.instance.isIpad();

Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Author

Created and maintained by VB10.

YouTube Medium Discord

"Buy Me A Coffee"

Contributors

Made with contrib.rocks.

Libraries

kartal