context_extensions_codespark 1.3.0 copy "context_extensions_codespark: ^1.3.0" to clipboard
context_extensions_codespark: ^1.3.0 copied to clipboard

BuildContext extensions for Flutter with MediaQuery helpers, responsive design utilities, snackbar and dialog helpers, theme shortcuts, typography, keyboard, and platform detection

context_extensions_codespark #

A lightweight Flutter package that provides BuildContext extensions, MediaQuery helpers, responsive design utilities, snackbar helpers, dialog helpers, theme shortcuts, typography utilities, keyboard management, and platform detection to reduce boilerplate and improve developer productivity.

Built by Katayath Sai Kiran · @Katayath-Sai-Kiran

pub version pub points pub likes license platform flutter BuildContext utilities

Screenshots #

Context Extensions Overview

Screen & MediaQuery Device & Platform Orientation
Snackbars Dialogs

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)
  • Navigation shortcuts (new in 1.3.0)

Flutter Responsive Design #

Build responsive and adaptive Flutter UIs using simple BuildContext extensions.

  • Device breakpoints
  • Window size classes
  • Orientation helpers
  • Adaptive typography
  • Responsive layouts
  • Screen size utilities

Perfect For #

  • BuildContext extensions
  • MediaQuery shortcuts
  • Responsive Flutter layouts
  • Adaptive UI development
  • Snackbar helpers
  • Dialog helpers
  • Theme access shortcuts
  • Typography utilities
  • Keyboard management
  • Platform detection
  • Flutter productivity
  • Reducing boilerplate code

Common Use Cases #

  • Flutter responsive design
  • MediaQuery replacements
  • Adaptive layouts
  • Context-based utilities
  • Snackbar management
  • Dialog management
  • Theme access
  • Typography access
  • Keyboard dismissal
  • Platform detection
  • Screen size calculations
  • Safe area calculations

Installation #

dependencies:
  context_extensions_codespark: latest
flutter pub get
import 'package:context_extensions_codespark/context_extensions_codespark.dart';

MediaQuery Extensions & Screen Size Helpers #

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

Responsive Design & Device 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 Extensions & Color Shortcuts #

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,
);

Snackbar Helpers & Context Snackbar Extensions #

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 */ },
  ),
);

Dialog Helpers & Context Dialog Extensions #

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,
    ),
  );
}

Clean shorthand for the most common Navigator calls — no more Navigator.of(context) boilerplate.

Without package:

Navigator.of(context).push(MaterialPageRoute(builder: (_) => DetailsPage()));
Navigator.of(context).pop();
Navigator.of(context).pushNamed('/home', arguments: {'id': 1});
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => HomePage()));
Navigator.of(context).pushAndRemoveUntil(
  MaterialPageRoute(builder: (_) => LoginPage()),
  (route) => false,
);
final canGoBack = Navigator.of(context).canPop();

With package:

context.push(MaterialPageRoute(builder: (_) => DetailsPage()));
context.pop();
context.pop('result');               // pop with a return value
context.pushNamed('/home', arguments: {'id': 1});
context.pushReplacement(MaterialPageRoute(builder: (_) => HomePage()));
context.pushAndRemoveUntil(
  MaterialPageRoute(builder: (_) => LoginPage()),
  (route) => false,
);
context.popUntilFirst();             // pops all routes back to root
context.canPop                       // true if there's a route to pop

Typical use — go to a page and come back:

// Go forward
context.push(MaterialPageRoute(builder: (_) => SettingsPage()));

// Go back with a result
final result = await context.push<String>(
  MaterialPageRoute(builder: (_) => PickerPage()),
);

// Replace current route (e.g. after login)
context.pushReplacement(MaterialPageRoute(builder: (_) => HomePage()));

// Clear the whole stack and go to login (e.g. logout)
context.pushAndRemoveUntil(
  MaterialPageRoute(builder: (_) => LoginPage()),
  (route) => false,
);

About #

Maintained by Katayath Sai Kiran.

Website: ksaikiran.dev

8
likes
160
points
120
downloads
screenshot

Documentation

API reference

Publisher

verified publisherksaikiran.dev

Weekly Downloads

BuildContext extensions for Flutter with MediaQuery helpers, responsive design utilities, snackbar and dialog helpers, theme shortcuts, typography, keyboard, and platform detection

Homepage
Repository (GitHub)
View/report issues

Topics

#buildcontext #responsive-design #flutter-extensions #mediaquery #flutter-utils

License

MIT (license)

Dependencies

flutter

More

Packages that depend on context_extensions_codespark