Adaptive Responsive UI

A comprehensive Flutter package for building responsive UIs with easy-to-use extensions. Scale your UI elements based on design dimensions from Figma, Adobe XD, or any design tool.

✨ NEW: Context-less extensions! Initialize once, use everywhere without passing context!

Features

  • 🎯 Easy Responsive Sizing - Scale width, height, fonts, icons, and radius
  • Context-less Extensions - Initialize once, use everywhere without context!
  • 📱 Device Detection - Identify mobile, tablet, and desktop devices
  • 🎨 Smart Padding & Margin - Responsive spacing helpers
  • 🔧 Configurable Base Dimensions - Match your exact design file dimensions
  • 📏 Safe Area Helpers - Easy access to safe area insets
  • 🎭 Responsive Widgets - Built-in responsive builder widget

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  adaptive_responsive_ui: ^1.0.5

Then run:

flutter pub get

Quick Start

Initialize once in your app

import 'package:flutter/material.dart';
import 'package:adaptive_responsive_ui/adaptive_responsive_ui.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Initialize ONCE with your Figma/design dimensions
    ResponsiveConfig.init(context, baseWidth: 390, baseHeight: 844);

    return MaterialApp(
      home: HomeScreen(),
    );
  }
}

Use anywhere without context! ✨

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        width: 200.w(),              // ✅ No context needed!
        height: 100.h(),             // ✅ No context needed!
        padding: 16.p,               // ✅ No context needed!
        margin: 20.m,                // ✅ No context needed!
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(12.r()),  // ✅ No context!
        ),
        child: Text(
          'Hello World',
          style: TextStyle(
            fontSize: 14.sp(),       // ✅ No context needed!
          ),
        ),
      ),
    );
  }
}

Usage Examples

After initializing ResponsiveConfig.init(context) once, use extensions everywhere without context:

// Sizing
Container(
  width: 300.w(),              // No context! ✅
  height: 200.h(),             // No context! ✅
)

// Typography
Text(
  'Title',
  style: TextStyle(fontSize: 24.sp()),  // No context! ✅
)

// Icons
Icon(Icons.home, size: 32.icon())      // No context! ✅

// Border Radius
BorderRadius.circular(16.r())          // No context! ✅

// Padding & Margin
Container(
  padding: 16.p,                       // No context! ✅
  margin: 20.m,                        // No context! ✅
  child: Text('Hello'),
)

// Symmetric padding/margin
Container(
  padding: 16.pSym(h: 24, v: 12),     // No context! ✅
  margin: 20.mSym(h: 16, v: 8),       // No context! ✅
)

// Gaps
Column(
  children: [
    Text('Item 1'),
    16.gapH,                           // No context! ✅
    Text('Item 2'),
  ],
)

Row(
  children: [
    Text('Left'),
    20.gapW,                           // No context! ✅
    Text('Right'),
  ],
)

📱 With Context (Alternative)

You can still use context-based extensions if needed:

Container(
  width: 200.w(context),
  height: 100.h(context),
  padding: 16.p(context),
  child: Text(
    'Hello',
    style: TextStyle(fontSize: 14.sp(context)),
  ),
)

// Full control with pOnly/pSymmetric
Container(
  padding: 20.pSymmetric(
    context: context,
    horizontal: 24,
    vertical: 12,
  ),
)

Container(
  padding: 10.pOnly(
    context: context,
    left: 20,
    top: 10,
    right: 20,
    bottom: 10,
  ),
)

// Gaps
Column(
  children: [
    Text('Item 1'),
    16.gapH(context),
    Text('Item 2'),
  ],
)

📱 Device Detection

// Check device type (requires context)
if (context.isMobile) {
  // Mobile layout
} else if (context.isTablet) {
  // Tablet layout
} else if (context.isDesktop) {
  // Desktop layout
}

// Get device type as string
print(context.deviceType); // "mobile", "tablet", or "desktop"

// Screen dimensions
print(context.screenWidth);
print(context.screenHeight);

// Orientation
if (context.isPortrait) {
  // Portrait mode
} else if (context.isLandscape) {
  // Landscape mode
}

🛡️ Safe Area

// Get safe area insets (requires context)
Padding(
  padding: EdgeInsets.only(
    top: context.safeTop,
    bottom: context.safeBottom,
    left: context.safeLeft,
    right: context.safeRight,
  ),
  child: YourWidget(),
)

🎯 Responsive Values

// Select values based on device type (requires context)
final columns = context.responsive<int>(
  mobile: 2,
  tablet: 3,
  desktop: 4,
);

// Grid count helper
GridView.count(
  crossAxisCount: context.gridCount(
    mobile: 2,
    tablet: 3,
    desktop: 4,
  ),
)

// Responsive value based on breakpoints
final fontSize = context.responsiveValue<double>(
  defaultValue: 14.0,
  sm: 12.0,    // < 600px
  md: 14.0,    // 600-1024px
  lg: 16.0,    // >= 1024px
);

🔧 Advanced Helpers

// Auto text size with constraints (requires context)
TextStyle(
  fontSize: context.autoTextSize(min: 12, max: 24),
)

// Responsive image size (requires context)
Size imgSize = context.imageSize(width: 300, height: 200);

// Min/Max scaling based on shortest/longest side
Container(
  width: 100.min(context),   // Based on shortest side
  height: 100.max(context),  // Based on longest side
)

// Responsive animation duration
Duration duration = context.animDuration(300); // 300ms base

// Card margin helper
Container(
  margin: context.cardMargin(16),
)

Configuration

Set Base Dimensions

You can set your design dimensions in multiple ways:

// Option 1: During initialization (Recommended)
ResponsiveConfig.init(context, baseWidth: 375, baseHeight: 812);

// Option 2: Before initialization
ResponsiveConfig.setBaseDimensions(width: 375, height: 812);
ResponsiveConfig.init(context);

// Option 3: Platform-specific
if (Platform.isIOS) {
  ResponsiveConfig.init(context, baseWidth: 390, baseHeight: 844);
} else {
  ResponsiveConfig.init(context, baseWidth: 360, baseHeight: 800);
}

Default Base Dimensions

If not specified, the default base dimensions are:

  • Width: 390 (iPhone 14 Pro)
  • Height: 844 (iPhone 14 Pro)

Check Configuration

// Get current base dimensions
print(ResponsiveConfig.baseWidth);   // 390.0
print(ResponsiveConfig.baseHeight);  // 844.0

// Check if initialized
print(ResponsiveConfig.initialized); // true/false

// Get current screen dimensions
print(ResponsiveConfig.width);       // Current screen width
print(ResponsiveConfig.height);      // Current screen height

Responsive Builder Widget

ResponsiveBuilder(
  builder: (context, constraints) {
    return Container(
      width: constraints.maxWidth,
      child: YourWidget(),
    );
  },
)

Breaking Points

  • Mobile: < 600px width
  • Tablet: 600px - 1024px width
  • Desktop: >= 1024px width

Complete Example

import 'package:flutter/material.dart';
import 'package:adaptive_responsive_ui/adaptive_responsive_ui.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Initialize ONCE
    ResponsiveConfig.init(context, baseWidth: 390, baseHeight: 844);

    return MaterialApp(
      title: 'Adaptive Responsive UI Demo',
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Responsive Demo'),
      ),
      body: SingleChildScrollView(
        padding: 16.p,  // No context needed! ✅
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Device Info
            Text(
              'Device: ${context.deviceType}',
              style: TextStyle(fontSize: 18.sp()),  // No context! ✅
            ),
            16.gapH,  // No context! ✅

            // Responsive Container
            Container(
              width: 300.w(),   // No context! ✅
              height: 150.h(),  // No context! ✅
              padding: 20.p,    // No context! ✅
              decoration: BoxDecoration(
                color: Colors.blue,
                borderRadius: BorderRadius.circular(12.r()),  // No context! ✅
              ),
              child: Center(
                child: Text(
                  'Responsive Container',
                  style: TextStyle(
                    fontSize: 16.sp(),  // No context! ✅
                    color: Colors.white,
                  ),
                ),
              ),
            ),
            20.gapH,  // No context! ✅

            // Symmetric Padding Example
            Container(
              padding: 16.pSym(h: 20, v: 10),  // No context! ✅
              color: Colors.green.shade100,
              child: Text(
                'Symmetric Padding',
                style: TextStyle(fontSize: 14.sp()),  // No context! ✅
              ),
            ),
            20.gapH,  // No context! ✅

            // Device Type Display
            Text('Screen: ${context.screenWidth.toInt()} x ${context.screenHeight.toInt()}'),
            Text('Mobile: ${context.isMobile}'),
            Text('Tablet: ${context.isTablet}'),
            Text('Desktop: ${context.isDesktop}'),
          ],
        ),
      ),
    );
  }
}

For more examples, check out the example folder.

API Reference

Context-less Extensions (After ResponsiveConfig.init())

Extension Description Example
.w() Responsive width 200.w()
.h() Responsive height 100.h()
.sp() Responsive font size 14.sp()
.icon() Responsive icon size 24.icon()
.r() Responsive radius 12.r()
.p Responsive padding (all sides) 16.p
.m Responsive margin (all sides) 20.m
.gapW Horizontal gap 16.gapW
.gapH Vertical gap 16.gapH
.pSym() Symmetric padding 16.pSym(h: 20, v: 10)
.mSym() Symmetric margin 16.mSym(h: 20, v: 10)

Context-based Extensions

Extension Description Example
.w(context) Responsive width 200.w(context)
.h(context) Responsive height 100.h(context)
.sp(context) Responsive font size 14.sp(context)
.p(context) Padding (all sides) 16.p(context)
.pOnly() Padding (specific sides) 16.pOnly(context: context, left: 20)
.pSymmetric() Symmetric padding 16.pSymmetric(context: context, horizontal: 20)
.m(context) Margin (all sides) 20.m(context)
.mOnly() Margin (specific sides) 20.mOnly(context: context, top: 10)
.mSymmetric() Symmetric margin 20.mSymmetric(context: context, vertical: 10)
.gapW(context) Horizontal gap 16.gapW(context)
.gapH(context) Vertical gap 16.gapH(context)
.min(context) Scale by shortest side 100.min(context)
.max(context) Scale by longest side 100.max(context)

BuildContext Extensions

Property Type Description
screenWidth double Current screen width
screenHeight double Current screen height
isMobile bool Is mobile device (< 600px)
isTablet bool Is tablet device (600-1024px)
isDesktop bool Is desktop device (>= 1024px)
deviceType String "mobile", "tablet", or "desktop"
isPortrait bool Is portrait orientation
isLandscape bool Is landscape orientation
safeTop double Top safe area padding
safeBottom double Bottom safe area padding
safeLeft double Left safe area padding
safeRight double Right safe area padding
pixelRatio double Device pixel ratio

Tips & Best Practices

  1. Initialize early: Call ResponsiveConfig.init(context) in your main app widget's build method
  2. Use design dimensions: Set baseWidth and baseHeight to match your Figma/design file exactly
  3. Context-less by default: Use context-less extensions (.w(), .sp(), etc.) for cleaner code
  4. Use context when needed: Use context-based extensions when you need more control (like .pOnly())
  5. Consistent scaling: Use .w() for horizontal, .h() for vertical measurements
  6. Font sizes: Always use .sp() for text to maintain readability across devices
  7. Test on devices: Test your responsive UI on different screen sizes (mobile, tablet, desktop)
  8. Hot restart after init: If you change base dimensions, do a hot restart (not hot reload)

Migration Guide

From version 1.0.x to 1.1.0

Version 1.1.0 introduces context-less extensions. No breaking changes, but you can now simplify your code:

Before (1.0.x):

Container(
  padding: 16.p(context),
  margin: 20.m(context),
  child: Text('Hello', style: TextStyle(fontSize: 14.sp(context))),
)

After (1.1.0):

Container(
  padding: 16.p,           // ✅ Simpler!
  margin: 20.m,            // ✅ Simpler!
  child: Text('Hello', style: TextStyle(fontSize: 14.sp())),  // ✅ Simpler!
)

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

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

Author

Mostafiz - GitHub

Support

If you find this package helpful, please:

Changelog

See CHANGELOG.md for a detailed list of changes.