baaba_extensions 0.4.0
baaba_extensions: ^0.4.0 copied to clipboard
Handy pack of extensions for the Flutter SDK — strings, numbers, lists, widgets, dates, booleans, BuildContext, and beautiful dialogs.
baaba_extensions #
A Flutter/Dart extension package that adds expressive, null-safe helpers to common types — strings, numbers, lists, widgets, dates, booleans, BuildContext, and beautiful customizable dialogs.
Installation #
Add to your pubspec.yaml:
dependencies:
baaba_extensions:
path: ../baaba_extensions # or your pub.dev version
Then import a single line in any file:
import 'package:baaba_extensions/baaba_extensions.dart';
Extensions #
StringExtension — on String? #
Null-safe string helpers. Call .validate() to get an empty string instead of null.
// Null safety
String? name;
name.validate(); // → ''
name.validate(value: 'N/A'); // → 'N/A'
name.isEmptyOrNull; // → true
name.isNullOrBlank; // → true
name.isNotBlank; // → false
// Validation
'user@example.com'.validateEmail(); // → true
'03001234567'.validatePhone(); // → true
'https://flutter.dev'.validateURL(); // → true
// Formatting
'hello world'.capitalizeFirstLetter(); // → 'Hello world'
'hello world'.capitalizeEachWord(); // → 'Hello World'
'helloWorld'.toSnakeCase(); // → 'hello_world'
'hello_world'.toCamelCase(); // → 'helloWorld'
'hello world'.toPascalCase(); // → 'HelloWorld'
'hello-world'.toKebabCase(); // → 'hello-world'
'John Doe'.initials(); // → 'JD'
// Truncation
'This is a long string'.ellipsize(10); // → 'This is...'
// Masking (for sensitive data display)
'user@example.com'.mask(); // → 'u***@example.com'
'03001234567'.mask(maskType: MaskType.phone); // → '03*******67'
'user@example.com'.mask(isMaskingEnabled: false);// → 'user@example.com'
// Conversion
'42'.toIntX(); // → 42
'3.14'.toDoubleX(); // → 3.14
'true'.toBool(); // → true
'hello'.toListX(); // → ['h','e','l','l','o']
'hello'.reverse; // → 'olleh'
// Checks
'hello'.isAlpha(); // → true
'123'.isDigit(); // → true
'{"a":1}'.isJson(); // → true
'image.png'.isImage; // → true
'file.pdf'.isPdf; // → true
'hello'.equalsIgnoreCase('HELLO'); // → true
// Clipboard
await 'copy me'.copyToClipboard();
// Word capitalisation
'hello world'.capitalizeAllWords(); // → 'Hello World'
// Misc
'1234567890'.formatNumberWithComma(); // → '1,234,567,890'
' hello world '.removeAllWhiteSpace(); // → 'helloworld'
'hello'.repeat(3, separator: '-'); // → 'hello-hello-hello'
'flutter widgets'.countWords(); // → 2
'flutter widgets'.toSlug(); // → 'flutter_widgets'
// Firebase search
'flutter'.setSearchParam(); // → ['f','fl','flu','flut','flutt','flutte','flutter']
// Pakistan mobile formatting
'03001234567'.formatPkMobile; // → '0300-1234567'
'03001234567'.toDisplayFormattedPhone;// → '0300-1234567'
// Toast
'Saved successfully!'.toastString(); // shows a toast
NumX / NumPaddingX — on num #
Quick spacing and layout helpers.
// SizedBox shortcuts
16.heightBox // SizedBox(height: 16)
16.widthBox // SizedBox(width: 16)
// EdgeInsets shortcuts
16.allPadding // EdgeInsets.all(16)
16.verticalPadding // EdgeInsets.symmetric(vertical: 16)
16.horizontalPadding // EdgeInsets.symmetric(horizontal: 16)
16.leftPadding // EdgeInsets.only(left: 16)
16.topPadding // EdgeInsets.only(top: 16)
16.rightPadding // EdgeInsets.only(right: 16)
16.bottomPadding // EdgeInsets.only(bottom: 16)
// Formatting
3.ordinal // → '3rd'
0.5.percentage // → 0.005
// Async delay
500.delay(() => print('done')); // runs after 500ms
NumDurationX / NumTimeX — on int #
Readable duration and relative time construction.
500.milliseconds // Duration(milliseconds: 500)
5.seconds // Duration(seconds: 5)
2.minutes // Duration(minutes: 2)
1.hours // Duration(hours: 1)
3.days // Duration(days: 3)
7.daysAgo // DateTime 7 days before now
2.hoursAgo // DateTime 2 hours before now
NumCoerceInExtension — on T extends num #
5.coerceIn(1, 10); // → 5 (within range)
0.coerceIn(1, 10); // → 1 (below min → clamps to min)
15.coerceIn(1, 10); // → 10 (above max → clamps to max)
ListX — on Iterable<T>? #
Null-safe iterable utilities.
List<int>? nums;
nums.validate(); // → []
[1, 2, 3].forEachIndexed((i, e) => print('$i: $e'));
[1, 3, 7].sumBy((n) => n); // → 11
['hi', 'world'].sumByDouble((s) => s.length); // → 7.0
[1, 2, 3].averageBy((n) => n); // → 2.0
// Group by key
users.groupBy((u) => u.role); // Map<Role, List<User>>
// Safe firstWhere
[1, 2, 3].firstWhereOrNull((n) => n > 5); // → null (no exception)
ListSplit — on List<T> #
[1, 2, 3, 4, 5].splitAt(2);
// → (before: [1, 2], after: [3, 4, 5])
[1, 2, 3, 4, 5].chunked(2);
// → [[1, 2], [3, 4], [5]]
[1, 2, 3, 4].partition((n) => n.isEven);
// → (matching: [2, 4], remaining: [1, 3])
ListSwapExtension — on List<E> #
final list = [1, 2, 3];
list.swap(0, 2); // → [3, 2, 1]
ListxWidgetExtensions — on List<Widget> #
Convert a plain list of widgets directly into a layout widget using method chaining.
[Text('A'), Text('B'), Text('C')].toRow();
[Text('A'), Text('B'), Text('C')].toColumn(mainAxisAlignment: MainAxisAlignment.center);
[Text('A'), Text('B'), Text('C')].toStack(alignment: Alignment.center);
// ListView (children mode)
[Text('A'), Text('B'), Text('C')].toList(shrinkWrap: true);
// ListView.builder
[Text('A'), Text('B'), Text('C')].toListView(
itemBuilder: (context, i) => ListTile(title: Text('Item $i')),
physics: const NeverScrollableScrollPhysics(),
);
All methods accept the same named parameters as their Flutter counterparts (mainAxisAlignment, crossAxisAlignment, scrollDirection, physics, padding, etc.).
ScrollxExtensions — on ScrollController #
Fluent helpers for common scroll operations.
final controller = ScrollController();
// Animate to any offset
await controller.animateToPosition(300);
// Jump to edges
await controller.animateToBottom();
await controller.animateToTop();
// Custom duration / curve
await controller.animateToBottom(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
// Guards
if (controller.isNearBottom(threshold: 80)) loadMoreItems();
if (controller.canScroll) showScrollIndicator();
| Member | Returns | Description |
|---|---|---|
animateToPosition(offset) |
Future<void> |
Animated scroll to a specific offset |
animateToBottom() |
Future<void> |
Animated scroll to maxScrollExtent |
animateToTop() |
Future<void> |
Animated scroll to minScrollExtent |
isNearBottom({threshold}) |
bool |
true when within threshold (default 50) px of bottom |
canScroll |
bool |
true if controller has clients and content overflows |
ContextX — on BuildContext #
Access theme, media query, and screen info from any widget.
context.theme // ThemeData
context.textTheme // TextTheme
context.colorScheme // ColorScheme
context.screenSize // Size
context.screenWidth // double
context.screenHeight // double
context.isMobile // true if width < 600
context.isTablet // true if 600 ≤ width < 1024
context.isDesktop // true if width ≥ 1024
context.hideKeyboard() // unfocus / dismiss keyboard
context.isKeyboardVisible// true if keyboard is open
DialogX — on BuildContext #
Beautiful, fully customizable confirmation and information dialogs.
Confirmation dialog
Returns true if confirmed, false if cancelled, null if dismissed by tapping outside.
final confirmed = await context.showConfirmDialog(
title: 'Delete Account',
message: 'All your data will be permanently removed.',
confirmText: 'Delete',
cancelText: 'Keep it',
confirmColor: Colors.red,
icon: Icons.delete_outline_rounded,
);
if (confirmed == true) deleteAccount();
| Parameter | Type | Default | Description |
|---|---|---|---|
title |
String |
required | Dialog title |
message |
String |
required | Body text |
confirmText |
String |
'Confirm' |
Confirm button label |
cancelText |
String |
'Cancel' |
Cancel button label |
onConfirm |
VoidCallback? |
— | Called after confirm tap |
onCancel |
VoidCallback? |
— | Called after cancel tap |
confirmColor |
Color? |
global | Confirm button + icon color |
cancelColor |
Color? |
global | Cancel button color |
icon |
IconData |
Icons.help_outline_rounded |
Icon shown at the top |
backgroundColor |
Color? |
theme surface | Dialog background |
titleStyle |
TextStyle? |
theme | Title text style |
messageStyle |
TextStyle? |
theme | Message text style |
borderRadius |
BorderRadius? |
global circular(24) |
Dialog corner radius |
barrierDismissible |
bool |
true |
Tap-outside to dismiss |
Information dialog
await context.showInfoDialog(
title: 'Profile Updated',
message: 'Your changes have been saved successfully.',
closeText: 'Great!',
icon: Icons.check_circle_outline_rounded,
accentColor: Colors.green,
);
| Parameter | Type | Default | Description |
|---|---|---|---|
title |
String |
required | Dialog title |
message |
String |
required | Body text |
closeText |
String |
'Got it' |
Close button label |
onClose |
VoidCallback? |
— | Called after close tap |
accentColor |
Color? |
global | Close button + icon color |
icon |
IconData |
Icons.info_outline_rounded |
Icon shown at the top |
backgroundColor |
Color? |
theme surface | Dialog background |
titleStyle |
TextStyle? |
theme | Title text style |
messageStyle |
TextStyle? |
theme | Message text style |
borderRadius |
BorderRadius? |
global circular(24) |
Dialog corner radius |
barrierDismissible |
bool |
true |
Tap-outside to dismiss |
DateTimeExt — on DateTime #
DateTime.now().timeAgo // → 'Just now' / '5 minutes ago' / etc.
DateTime.now().isToday // → true
DateTime.now().isYesterday
DateTime.now().isTomorrow
DateTime.now().isFuture
DateTime.now().isPast
DateTime.now().startOfDay // → DateTime(year, month, day, 0, 0, 0)
DateTime.now().endOfDay // → DateTime(year, month, day, 23, 59, 59, 999)
date1.isSameDay(date2) // → true / false
Top-level helpers:
currentMillisecondsTimeStamp() // → current epoch ms
currentTimeStamp() // → current epoch seconds
leapYear(2024) // → true
daysInMonth(2, 2024) // → 29
WidgetX — on Widget #
Wrap widgets with common layout wrappers using method chaining.
Text('Hello').center()
Text('Hello').expanded()
Text('Hello').expanded(flex: 2)
Text('Hello').withWidth(100)
Text('Hello').withHeight(50)
Text('Hello').withSize(width: 100, height: 50)
Text('Hello').visible(isLoggedIn)
Text('Hello').cornerRadiusWithClipRRect(12.0)
Text('Hello').cornerRadiusWithClipRRectOnly(topLeft: 12, topRight: 12)
Text('Hello').onTap(() => doSomething())
BoolxExtensions — on bool #
true.isTrue // → true
true.isFalse // → false
true.toggle // → false
Widgets #
Ready-made Flutter widgets exported from the package.
VxTextBuilder #
A fluent, chainable text-styling builder backed by AutoSizeText. Chain as many modifiers as needed, then call .make() to produce a Widget.
// Basic usage
VxTextBuilder('Hello World')
.bold
.center
.blue500
.xl2
.make();
// Via extension on an existing Text widget
Text('Hello').text
.semiBold
.ellipsis
.red600
.make();
Font weights
'text'.text.hairLine.make() // w100
'text'.text.thin.make() // w200
'text'.text.light.make() // w300
'text'.text.normal.make() // w400
'text'.text.medium.make() // w500
'text'.text.semiBold.make() // w600
'text'.text.bold.make() // w700
'text'.text.extraBold.make() // w800
'text'.text.extraBlack.make()// w900
Scale / size
'text'.text.xs.make() // 0.75×
'text'.text.sm.make() // 0.875×
'text'.text.base.make() // 1× (default)
'text'.text.lg.make() // 1.125×
'text'.text.xl.make() // 1.25×
'text'.text.xl2.make() // 1.5×
'text'.text.xl3.make() // 1.875×
'text'.text.xl4.make() // 2.25×
'text'.text.xl5.make() // 3×
'text'.text.xl6.make() // 4×
VxTextBuilder('text').size(20).make() // exact px
Alignment
.center .start .end .justify
// or custom:
.align(TextAlign.left)
Text transforms
.uppercase // → 'HELLO WORLD'
.lowercase // → 'hello world'
.capitalize // → 'Hello World'
Overflow
.ellipsis .fade .visible
// or: .overflow(TextOverflow.clip)
Decoration
.underline .lineThrough .overline
Letter spacing
.tightest .tighter .tight // -3, -2, -1
.wide .wider .widest // 1, 2, 3
// or custom: .letterSpacing(0.5)
Line height
.heightTight // 0.75
.heightSnug // 0.875
.heightRelaxed // 1.25
.heightLoose // 1.5
// or custom: .lineHeight(1.4)
Shadow
VxTextBuilder('text')
.shadow(2, 2, 4, Colors.black38)
.make();
// individual:
.shadowBlur(4).shadowColor(Colors.black38).shadowOffset(2, 2)
Colors — full Tailwind-scale palette:
.white .black .transparent
.gray50 … .gray900
.red50 … .red900
.blue50 … .blue900
// and: slate, zinc, neutral, stone, orange, amber, yellow, lime,
// green, emerald, teal, cyan, sky, indigo, violet, purple,
// fuchsia, pink, rose — each with 50–900 shades
// or explicit: .color(Colors.deepOrange)
TextTheme integration
VxTextBuilder('Heading').displayLarge(context).make()
VxTextBuilder('Body').bodyMedium(context).make()
// all M3 roles: displayLarge/Medium/Small, headlineLarge/Medium/Small,
// titleLarge/Medium/Small, bodyLarge/Medium/Small, labelLarge/Medium/Small
Auto-size controls
VxTextBuilder('text')
.minFontSize(10)
.maxFontSize(30)
.stepGranularity(0.5)
.maxLines(2)
.overflowReplacement(Text('…'))
.make();
Conditional rendering
VxTextBuilder('Admin only').when(user.isAdmin).make();
// renders SizedBox.shrink() when false
Intrinsic mode — disables AutoSizeText for widgets that don't work with LayoutBuilder:
VxTextBuilder('text').isIntrinsic.make();
VxTextExtensions — on Text
Convert any Text widget into a VxTextBuilder for further styling:
Text('Hello').text.bold.blue600.xl.make()
SwiperWidgetx #
A fully-featured carousel/page-swiper widget.
// From a list
SwiperWidgetx(
items: [
Image.asset('a.png'),
Image.asset('b.png'),
],
autoPlay: true,
autoPlayInterval: const Duration(seconds: 4),
enlargeCenterPage: true,
onPageChanged: (i) => print('page $i'),
);
// From a builder (efficient for large/dynamic lists)
SwiperWidgetx.builder(
itemCount: 20,
itemBuilder: (context, i) => Card(child: Text('$i')),
viewportFraction: 0.9,
scrollDirection: Axis.vertical,
);
| Parameter | Type | Default | Description |
|---|---|---|---|
items / itemBuilder |
List<Widget> / IndexedWidgetBuilder |
required | Content source |
itemCount |
int |
— | Required for .builder constructor |
height |
double? |
— | Fixed height; if null uses aspectRatio |
aspectRatio |
double |
16/9 |
Aspect ratio when height is null |
viewportFraction |
num |
0.8 |
Fraction of viewport each page occupies |
enableInfiniteScroll |
bool |
true |
Loop pages infinitely |
autoPlay |
bool |
false |
Auto-advance pages |
autoPlayInterval |
Duration |
5s |
Interval between auto advances |
autoPlayAnimationDuration |
Duration |
800ms |
Animation duration for auto-play |
autoPlayCurve |
Curve |
fastOutSlowIn |
Animation curve for auto-play |
enlargeCenterPage |
bool? |
false |
Scale the center page up |
scrollDirection |
Axis |
horizontal |
Scroll axis |
isFastScrollingEnabled |
bool |
false |
Allow flinging multiple pages |
onPageChanged |
Function(int)? |
— | Called on page change |
reverse |
bool |
false |
Reverse scroll direction |
Programmatic control — get a reference via a GlobalKey<SwiperWidgetxState>:
final key = GlobalKey<SwiperWidgetxState>();
SwiperWidgetx(key: key, items: [...]);
key.currentState?.nextPage(duration: 300.milliseconds, curve: Curves.ease);
key.currentState?.previousPage(duration: 300.milliseconds, curve: Curves.ease);
key.currentState?.jumpToPage(2);
key.currentState?.animateToPage(2, duration: 300.milliseconds, curve: Curves.ease);
HorizontalListWithoutHeight #
A horizontally-scrolling Wrap-based list that sizes itself to its content — no fixed height required.
HorizontalListWithoutHeight(
itemCount: tags.length,
itemBuilder: (context, i) => Chip(label: Text(tags[i])),
spacing: 8,
runSpacing: 4,
paddings: const EdgeInsets.symmetric(horizontal: 16),
);
| Parameter | Type | Default | Description |
|---|---|---|---|
itemCount |
int |
required | Number of items |
itemBuilder |
IndexedWidgetBuilder |
required | Item builder |
spacing |
double? |
— | Horizontal gap between items |
runSpacing |
double? |
— | Vertical gap between runs |
paddings |
EdgeInsets? |
— | Outer padding |
physics |
ScrollPhysics? |
— | Scroll physics override |
controller |
ScrollController? |
— | External scroll controller |
reverse |
bool |
false |
Reverse scroll direction |
wrapAlignment |
WrapAlignment? |
— | Main-axis alignment |
crossAxisAlignment |
WrapCrossAlignment? |
— | Cross-axis alignment |
ReadMoreWidgetx #
A text widget that collapses long content with a "Show more" / "Show less" toggle.
ReadMoreWidgetx(
data: longText,
trimLines: 3,
trimMode: TrimMode.Line,
trimCollapsedText: 'Read more',
trimExpandedText: 'Read less',
colorClickableText: Colors.blue,
style: const TextStyle(fontSize: 14),
);
| Parameter | Type | Default | Description |
|---|---|---|---|
data |
String |
required | Text content |
trimMode |
TrimMode |
TrimMode.Length |
Trim by character count or line count |
trimLength |
int |
200 |
Max characters (for TrimMode.Length) |
trimLines |
int |
2 |
Max lines (for TrimMode.Line) |
trimCollapsedText |
String |
'Show more' |
Label when collapsed |
trimExpandedText |
String |
'Show less' |
Label when expanded |
colorClickableText |
Color? |
— | Color of the toggle link |
style |
TextStyle? |
— | Text style |
TrimMode enum:
TrimMode.Length // trim by character count
TrimMode.Line // trim by line count
RestartAppWidgetx #
Wraps your app root to allow programmatic hot-restart from anywhere in the tree.
// In main.dart
runApp(RestartAppWidgetx(child: MyApp()));
// Anywhere in the tree
RestartAppWidgetx.init(context); // restarts the app
Utils #
Patterns #
Static regex strings for common validation:
Patterns.email
Patterns.emailEnhanced
Patterns.pkMobileLocal // 03xxxxxxxxx
Patterns.pkMobileGlobal // +92 / 0092 / 0 prefix
Patterns.url
Patterns.image // jpeg, jpg, gif, png, bmp
Patterns.audio // mp3, wav, ogg, etc.
Patterns.video // mp4, avi, mkv, etc.
Patterns.pdf
Patterns.doc
Patterns.excel
Patterns.ppt
Patterns.apk
Patterns.txt
Patterns.html
MaskType enum #
MaskType.auto // auto-detect email or phone
MaskType.email
MaskType.phone
TrimMode enum #
Used by ReadMoreWidgetx to choose how text is trimmed.
TrimMode.Length // trim after N characters (default)
TrimMode.Line // trim after N lines
Global Toast Config #
Override these before showing any toasts:
defaultToastBackgroundColor = Colors.black87;
defaultToastTextColor = Colors.white;
defaultToastGravityGlobal = ToastGravity.BOTTOM;
Global Dialog Config #
Override these once (e.g. in main()) to restyle all dialogs app-wide:
defaultDialogConfirmColorGlobal = Colors.deepPurple;
defaultDialogCancelColorGlobal = Colors.grey.shade700;
defaultDialogInfoColorGlobal = Colors.green;
defaultDialogBorderRadiusGlobal = BorderRadius.circular(16);
Requirements #
- Dart SDK
^3.11.3 - Flutter
>=1.17.0 fluttertoast^9.0.0— used byStringExtension.toastString()flutter_auto_size_text^5.0.0— used byVxTextBuilder