context_extensions_codespark 1.1.0
context_extensions_codespark: ^1.1.0 copied to clipboard
Powerful and lightweight BuildContext extensions for Flutter. Simplify navigation, responsive layouts, theme access, dialogs, snackbars, and more.
context_extensions_codespark #
A lightweight Flutter package that provides powerful and convenient BuildContext extensions to reduce boilerplate and improve developer productivity.
Built by Katayath Sai Kiran · @Katayath-Sai-Kiran
Screenshots #
| Screen & MediaQuery | Device & Platform | Theme & Colors | Typography |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Snackbars | |||
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Dialogs | Orientation | ||
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
Features #
- Screen & MediaQuery utilities
- Theme & Color shortcuts
- Typography helpers with adaptive sizing
- Responsive device helpers (M3 window size classes)
- Snackbar helpers (with full customization)
- Dialog utilities (alert, confirm, loading, custom)
- Focus & keyboard management
- Orientation helpers
- Post-frame callbacks with mount safety
- Platform detection (web-safe)
Installation #
dependencies:
context_extensions_codespark: latest
flutter pub get
import 'package:context_extensions_codespark/context_extensions_codespark.dart';
Screen & MediaQuery #
Without package:
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
double top = MediaQuery.of(context).padding.top;
double bottom = MediaQuery.of(context).padding.bottom;
double keyboard = MediaQuery.of(context).viewInsets.bottom;
With package:
double width = context.width;
double height = context.height;
double top = context.safeTop;
double bottom = context.safeBottom;
double keyboard = context.bottomInset;
Other available properties:
context.devicePixelRatio
context.textScaler
context.platformBrightness
context.padding
context.viewPadding
context.statusBarHeight
Device & Responsive Helpers #
Based on Material Design 3 Window Size Classes.
Without package:
final width = MediaQuery.of(context).size.width;
final isMobile = width < 600;
final isTablet = width >= 600 && width < 840;
final isExpanded = width >= 840 && width < 1200;
final isDesktop = width >= 1200 && width < 1600;
final isXL = width >= 1600;
final isKeyboardOpen = MediaQuery.of(context).viewInsets.bottom > 0;
With package:
context.isMobile // < 600 dp — compact (phone portrait)
context.isTablet // 600–839 dp — medium (tablet portrait)
context.isExpanded // 840–1199 dp — expanded (tablet landscape)
context.isDesktop // 1200–1599 dp — large (desktop)
context.isExtraLarge // >= 1600 dp — extra-large (ultra-wide)
context.isKeyboardOpen
Display hardware hints (from MediaQuery — no platform API needed):
Without package:
final isHighDensity = MediaQuery.of(context).devicePixelRatio > 2.0;
final hasNotch = MediaQuery.of(context).padding.top > 24.0;
final hasHomeBar = MediaQuery.of(context).padding.bottom > 0.0;
With package:
context.isHighDensityDisplay // pixel ratio > 2.0
context.hasNotch // safeTop > 24 dp
context.hasHomeIndicator // safeBottom > 0 dp
Platform detection — web-safe:
Without package:
import 'dart:io';
// Throws on web!
if (Platform.isAndroid) { ... }
if (Platform.isIOS) { ... }
With package (guarded with kIsWeb internally):
if (context.isAndroid) { ... }
if (context.isIOS) { ... }
if (context.isWeb) { ... }
if (context.isMacOS) { ... }
if (context.isWindows) { ... }
if (context.isLinux) { ... }
if (context.isFuchsia) { ... }
// Grouped helpers
if (context.isNativeMobile) { ... } // Android || iOS
if (context.isNativeDesktop) { ... } // macOS || Windows || Linux
if (context.isTouchDevice) { ... } // Android || iOS || Fuchsia
if (context.isPointerDevice) { ... } // macOS || Windows || Linux || Web
Theme & Colors #
Without package:
final scheme = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.dark;
final primary = Theme.of(context).colorScheme.primary;
final error = Theme.of(context).colorScheme.error;
With package:
final scheme = context.colorScheme;
final isDark = context.isDarkMode;
final primary = context.primaryColor;
final error = context.errorColor;
Full color palette:
context.primaryColor context.onPrimaryColor
context.secondaryColor context.onSecondaryColor
context.tertiaryColor context.onTertiaryColor
context.surfaceColor context.onSurfaceColor
context.errorColor context.onErrorColor
context.outlineColor
context.shadowColor
context.scrimColor
context.inverseSurfaceColor
context.surfaceTintColor
Typography #
All style getters return a safe TextStyle() fallback — never null, never throws.
Without package:
Text('Hello', style: Theme.of(context).textTheme.titleLarge);
With package:
Text('Hello', style: context.titleLarge);
All styles:
context.displayLarge context.displayMedium context.displaySmall
context.headlineLarge context.headlineMedium context.headlineSmall
context.titleLarge context.titleMedium context.titleSmall
context.bodyLarge context.bodyMedium context.bodySmall
context.labelLarge context.labelMedium context.labelSmall
Font scale (system accessibility setting):
Without package:
final scale = MediaQuery.of(context).textScaler.scale(1.0);
if (scale > 1.3) { /* compact layout */ }
With package:
if (context.fontScale > 1.3) { /* compact layout */ }
Scale a specific style to the system font size:
Without package:
final fontSize = Theme.of(context).textTheme.bodyMedium?.fontSize ?? 14.0;
final scaled = Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: MediaQuery.of(context).textScaler.scale(fontSize),
);
With package:
final scaled = context.scaled(context.bodyMedium);
Adapt style based on screen width:
Without package:
final width = MediaQuery.of(context).size.width;
final style = width < 600
? Theme.of(context).textTheme.bodyMedium!
: Theme.of(context).textTheme.titleLarge!;
With package:
final style = context.adaptiveStyle(
mobile: context.bodyMedium,
desktop: context.titleLarge,
);
Snackbars #
Without package:
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Saved successfully'), backgroundColor: Colors.green),
);
With package:
context.showSuccess('Saved successfully');
All snackbar helpers:
context.showSnackBar('Hello!');
context.showSuccess('Saved successfully');
context.showError('Something went wrong');
context.showInfo('Profile updated');
With custom options (all params optional):
context.showSuccess(
'Item deleted',
duration: const Duration(seconds: 4),
behavior: SnackBarBehavior.floating,
textColor: Colors.white,
backgroundColor: Colors.green,
action: SnackBarAction(
label: 'Undo',
onPressed: () { /* restore */ },
),
);
Dialogs #
Alert dialog #
Without package:
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Session Expired'),
content: const Text('Please log in again to continue.'),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('OK'),
),
],
),
);
With package:
await context.showAlertDialog(
title: 'Session Expired',
message: 'Please log in again to continue.',
confirmLabel: 'OK',
);
Confirmation dialog #
Returns true if confirmed, false if cancelled.
Without package:
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Delete Account'),
content: const Text('This action cannot be undone. Are you sure?'),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(false),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.of(ctx).pop(true),
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('Delete'),
),
],
),
);
if (confirmed ?? false) { /* proceed */ }
With package:
final confirmed = await context.showConfirmDialog(
title: 'Delete Account',
message: 'This action cannot be undone. Are you sure?',
confirmLabel: 'Delete',
isDestructive: true,
);
if (confirmed) { /* proceed */ }
Loading dialog #
Without package:
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => const AlertDialog(
content: Row(children: [
CircularProgressIndicator(),
SizedBox(width: 16),
Text('Uploading...'),
]),
),
);
await uploadFile();
if (context.mounted) Navigator.of(context).pop();
With package:
context.showLoadingDialog(message: 'Uploading...');
try {
await uploadFile();
} finally {
context.hideDialog();
}
Custom dialog #
await context.showCustomDialog(
builder: (context) => MyCustomDialogWidget(),
);
Focus & Keyboard #
Without package:
FocusScope.of(context).unfocus();
FocusScope.of(context).nextFocus();
FocusScope.of(context).previousFocus();
FocusScope.of(context).requestFocus(myFocusNode);
final hasFocus = FocusScope.of(context).hasFocus;
final primaryNode = FocusManager.instance.primaryFocus;
With package:
context.unfocus();
context.nextFocus();
context.previousFocus();
context.requestFocus(myFocusNode);
context.isScopeFocused
context.hasAnyFocus
context.primaryFocusNode
context.focusScopeNode
Dismiss keyboard when tapping outside a field:
GestureDetector(
onTap: () => context.unfocus(),
child: Scaffold(body: TextField()),
);
Navigate between fields on submit:
TextField(
onSubmitted: (_) => context.nextFocus(),
),
Post-Frame Callbacks #
Runs code after the widget tree renders. Safe replacements for WidgetsBinding.instance.addPostFrameCallback(...).
Without package:
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollController.animateTo(...);
});
With package:
context.onPostFrame(() {
_scrollController.animateTo(...);
});
Wait multiple frames before acting (e.g. measuring a widget after nested rebuilds):
Without package:
// Messy manual chaining
WidgetsBinding.instance.addPostFrameCallback((_) {
WidgetsBinding.instance.addPostFrameCallback((_) {
final box = myKey.currentContext?.findRenderObject() as RenderBox?;
print(box?.size);
});
});
With package:
context.onPostFrames(2, () {
final box = myKey.currentContext?.findRenderObject() as RenderBox?;
print(box?.size);
});
Check if context is still mounted after async gap:
Without package:
await uploadFile();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(...);
}
With package:
await uploadFile();
context.ifMounted(() => context.showSuccess('Uploaded!'));
// Or check manually:
if (context.isContextMounted) {
context.showSuccess('Uploaded!');
}
Post-frame + mounted safety combined:
// Safest pattern — only runs if still mounted after the frame
context.onPostFrameIfMounted(() {
context.showInfo('Welcome back!');
});
Orientation Helpers #
Without package:
final isPortrait = MediaQuery.of(context).size.height >
MediaQuery.of(context).size.width;
final padding = isPortrait ? 16.0 : 48.0;
final columns = isPortrait ? 2 : 4;
With package:
context.isPortrait
context.isLandscape
final padding = context.valueByOrientation(portrait: 16.0, landscape: 48.0);
final columns = context.valueByOrientation(portrait: 2, landscape: 4);
Full adaptive grid example:
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(
horizontal: context.valueByOrientation(portrait: 16.0, landscape: 48.0),
),
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: context.valueByOrientation(portrait: 2, landscape: 4),
),
itemBuilder: (_, i) => ItemCard(),
itemCount: items.length,
),
);
}
About #
Maintained by Katayath Sai Kiran.
Website: ksaikiran.dev











