flutter_responsive_plus 1.0.1 copy "flutter_responsive_plus: ^1.0.1" to clipboard
flutter_responsive_plus: ^1.0.1 copied to clipboard

The ultimate Flutter responsive & adaptive toolkit. Combines MediaQuery shortcuts, ScreenUtil-style Figma scaling, LayoutBuilder helpers, platform detection, breakpoint management, and extension-based sizing.

example/lib/main.dart

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

void main() => runApp(const ExampleApp());

// ─────────────────────────────────────────────────────────────────────────────
// APP ROOT  — multi-canvas Figma config + ResKit.builder demo
// ─────────────────────────────────────────────────────────────────────────────
class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ResponsiveKit(
      config: ResKitConfig(
        // ✅ Per-breakpoint Figma canvases — one per device category.
        // Each canvas matches the exact frame size your designer used in Figma.
        designs: ResKitDesignConfig(
          mobile:  ResKitDesignSize.iphone14,          // 390 × 844
          tablet:  ResKitDesignSize.ipadMini,           // 768 × 1024
          desktop: ResKitDesignSize.macbookAir,         // 1440 × 900
          web:     ResKitDesignSize.webBrowser,         // 1280 × 800
          // Optional fine-grained overrides:
          // tabletSmall:  ResKitDesignSize(width: 600, height: 900),
          // tabletLarge:  ResKitDesignSize(width: 1024, height: 1366),
          // desktopLarge: ResKitDesignSize(width: 1920, height: 1080),
        ),
        maxFontScale: 1.3,
        breakpoints: ResKitBreakpoints(), // default or customise
      ),
      child: MaterialApp(
        title: 'flutter_responsive_plus Demo',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
        // ✅ ResKit.builder at the MaterialApp home level — the entire app
        // renders the right layout for the current screen size.
        home: ResKit.builder(
          mobileBuilder:       (ctx) => const MobileApp(),
          tabletBuilder:       (ctx) => const TabletApp(),
          desktopBuilder:      (ctx) => const DesktopApp(),
          webBuilder:          (ctx) => const WebApp(),
          desktopLargeBuilder: (ctx) => const UltraWideApp(),
          onLayout: (info) {
            // ignore: avoid_print
            print('[ResKit] Active layout: ${info.layoutType} | '
                'Canvas: ${info.activeDesign} | '
                'Screen: ${info.screenWidth.toStringAsFixed(0)}×'
                '${info.screenHeight.toStringAsFixed(0)}');
          },
        ),
      ),
    );
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// MOBILE APP  (Figma canvas: 390 × 844 — auto-applied)
// ─────────────────────────────────────────────────────────────────────────────
class MobileApp extends StatefulWidget {
  const MobileApp({super.key});
  @override State<MobileApp> createState() => _MobileAppState();
}

class _MobileAppState extends State<MobileApp> {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) => ResKit.debugPrint());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('📱 Mobile', style: TextStyle(fontSize: 18.sp)),
        backgroundColor: Colors.indigo,
        foregroundColor: Colors.white,
      ),
      body: SingleChildScrollView(
        padding: EdgeInsets.all(16.r),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _ActiveCanvasBanner(),
            SizedBox(height: 16.h),
            _SectionHeader('Multi-Canvas Figma Scaling'),
            _MultiCanvasDemo(),
            SizedBox(height: 16.h),
            _SectionHeader('ResKit.builder slots'),
            _BuilderSlotsDemo(),
            SizedBox(height: 16.h),
            _SectionHeader('All Sizing API'),
            _SizingApiDemo(),
            SizedBox(height: 16.h),
            _SectionHeader('Breakpoints'),
            _BreakpointDemo(),
            SizedBox(height: 16.h),
            _SectionHeader('Platform'),
            _PlatformDemo(),
          ],
        ),
      ),
    );
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// TABLET APP  (Figma canvas: 768 × 1024 — auto-applied)
// ─────────────────────────────────────────────────────────────────────────────
class TabletApp extends StatelessWidget {
  const TabletApp({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('📟 Tablet', style: TextStyle(fontSize: 18.sp)),
        backgroundColor: Colors.green.shade700,
        foregroundColor: Colors.white,
      ),
      body: Row(
        children: [
          // Sidebar — only shown on tablet+
          Container(
            width: 220.w,
            color: Colors.green.shade50,
            child: Column(
              children: [
                SizedBox(height: 24.h),
                _ActiveCanvasBanner(),
                SizedBox(height: 16.h),
                _NavItem(icon: Icons.home, label: 'Home'),
                _NavItem(icon: Icons.dashboard, label: 'Dashboard'),
                _NavItem(icon: Icons.settings, label: 'Settings'),
              ],
            ),
          ),
          // Main content
          Expanded(
            child: SingleChildScrollView(
              padding: EdgeInsets.all(24.r),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  _SectionHeader('Tablet layout — 768×1024 Figma canvas active'),
                  _MultiCanvasDemo(),
                  SizedBox(height: 16.h),
                  _SectionHeader('2-column grid (tablet breakpoint)'),
                  _TwoColumnGrid(),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// DESKTOP APP  (Figma canvas: 1440 × 900 — auto-applied)
// ─────────────────────────────────────────────────────────────────────────────
class DesktopApp extends StatelessWidget {
  const DesktopApp({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          // Wide sidebar
          Container(
            width: 240.w,
            color: Colors.purple.shade50,
            padding: EdgeInsets.symmetric(vertical: 24.h),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Padding(
                  padding: EdgeInsets.symmetric(horizontal: 20.w),
                  child: Text('🖥️ Desktop',
                      style: TextStyle(fontSize: 20.sp, fontWeight: FontWeight.w700,
                          color: Colors.purple.shade800)),
                ),
                SizedBox(height: 24.h),
                _ActiveCanvasBanner(),
                SizedBox(height: 16.h),
                _NavItem(icon: Icons.home, label: 'Home'),
                _NavItem(icon: Icons.analytics, label: 'Analytics'),
                _NavItem(icon: Icons.people, label: 'Users'),
                _NavItem(icon: Icons.settings, label: 'Settings'),
              ],
            ),
          ),
          // Main area — centred & width-capped
          Expanded(
            child: WebContentWrapper(
              maxWidth: ResKit.breakpoints.webMaxContent,
              child: SingleChildScrollView(
                padding: EdgeInsets.all(32.r),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    _SectionHeader('Desktop — 1440×900 Figma canvas active'),
                    _MultiCanvasDemo(),
                    SizedBox(height: 16.h),
                    _SectionHeader('4-column grid (desktop breakpoint)'),
                    _FourColumnGrid(),
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// WEB APP  (Figma canvas: 1280 × 800 — platform override, active on web)
// ─────────────────────────────────────────────────────────────────────────────
class WebApp extends StatelessWidget {
  const WebApp({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('🌐 Web', style: TextStyle(fontSize: 18.sp)),
        backgroundColor: Colors.orange.shade700,
        foregroundColor: Colors.white,
      ),
      body: WebContentWrapper(
        maxWidth: 1280,
        padding: EdgeInsets.symmetric(horizontal: 40.w),
        child: SingleChildScrollView(
          padding: EdgeInsets.symmetric(vertical: 32.h),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              _ActiveCanvasBanner(),
              SizedBox(height: 16.h),
              _SectionHeader('Web layout — 1280×800 canvas (platform override)'),
              _MultiCanvasDemo(),
              SizedBox(height: 16.h),
              _SectionHeader('Fluid 3-column grid'),
              _ThreeColumnGrid(),
            ],
          ),
        ),
      ),
    );
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// ULTRA-WIDE APP  (desktopLarge — 1920 × 1080)
// ─────────────────────────────────────────────────────────────────────────────
class UltraWideApp extends StatelessWidget {
  const UltraWideApp({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('🖥️🖥️ Ultra-wide Desktop', style: TextStyle(fontSize: 18.sp)),
        backgroundColor: Colors.deepPurple,
        foregroundColor: Colors.white,
      ),
      body: WebContentWrapper(
        maxWidth: 1600,
        child: SingleChildScrollView(
          padding: EdgeInsets.all(40.r),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              _ActiveCanvasBanner(),
              SizedBox(height: 20.h),
              _SectionHeader('Ultra-wide — desktopLarge slot active'),
              _MultiCanvasDemo(),
            ],
          ),
        ),
      ),
    );
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// SHARED DEMO WIDGETS
// ─────────────────────────────────────────────────────────────────────────────

/// Banner showing the currently-active Figma canvas.
class _ActiveCanvasBanner extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final design = ResKit.activeDesign;
    final type   = ResKit.deviceType;
    final color  = switch (type) {
      ResKitDeviceType.mobile  => Colors.blue,
      ResKitDeviceType.tablet  => Colors.green,
      ResKitDeviceType.desktop => Colors.purple,
    };
    return Container(
      width: double.infinity,
      padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
      decoration: BoxDecoration(
        color: color.withAlpha(20),
        borderRadius: BorderRadius.circular(8.r),
        border: Border.all(color: color.withAlpha(80)),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('Active Figma Canvas',
              style: TextStyle(fontSize: 11.sp, color: color.withAlpha(180),
                  fontWeight: FontWeight.w600, letterSpacing: 0.5)),
          SizedBox(height: 4.h),
          Text('${design.width.toStringAsFixed(0)} × ${design.height.toStringAsFixed(0)} dp',
              style: TextStyle(fontSize: 20.sp, fontWeight: FontWeight.w800, color: color)),
          Text('scaleW: ${ResKit.scaleW.toStringAsFixed(3)}  '
               'scaleH: ${ResKit.scaleH.toStringAsFixed(3)}  '
               'scale: ${ResKit.scale.toStringAsFixed(3)}',
              style: TextStyle(fontSize: 11.sp, color: color.withAlpha(160),
                  fontFamily: 'monospace')),
        ],
      ),
    );
  }
}

class _MultiCanvasDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final d = ResKit.config.designs;
    return _Card(children: [
      _Row('activeDesign',      '${ResKit.activeDesign}'),
      _Row('figmaWidth (active)','${ResKit.figmaWidth.toStringAsFixed(0)}'),
      _Row('figmaHeight (active)','${ResKit.figmaHeight.toStringAsFixed(0)}'),
      if (d != null) ...[
        const Divider(),
        Text('Configured canvases:',
            style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.w600)),
        SizedBox(height: 4.h),
        if (d.mobile  != null) _Row('  mobile',  '${d.mobile}'),
        if (d.tablet  != null) _Row('  tablet',  '${d.tablet}'),
        if (d.desktop != null) _Row('  desktop', '${d.desktop}'),
        if (d.web     != null) _Row('  web',     '${d.web}'),
        if (d.tabletSmall  != null) _Row('  tabletSmall',  '${d.tabletSmall}'),
        if (d.tabletLarge  != null) _Row('  tabletLarge',  '${d.tabletLarge}'),
        if (d.desktopLarge != null) _Row('  desktopLarge', '${d.desktopLarge}'),
      ],
    ]);
  }
}

class _BuilderSlotsDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return _Card(children: [
      Text('ResKit.builder() — inline slot test:',
          style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.w600)),
      SizedBox(height: 8.h),
      ResKit.builder(
        mobile:  _SlotChip('mobile',       Colors.blue),
        tablet:  _SlotChip('tablet',       Colors.green),
        desktop: _SlotChip('desktop',      Colors.purple),
        web:     _SlotChip('web',          Colors.orange),
        desktopLarge: _SlotChip('desktopLarge', Colors.deepPurple),
        desktopXL:    _SlotChip('desktopXL',    Colors.red),
      ),
      SizedBox(height: 8.h),
      Text('context.responsiveBuilder() — context shortcut:',
          style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.w600)),
      SizedBox(height: 8.h),
      context.responsiveBuilder(
        mobile:  _SlotChip('mobile ctx',   Colors.blue),
        tablet:  _SlotChip('tablet ctx',   Colors.green),
        desktop: _SlotChip('desktop ctx',  Colors.purple),
        web:     _SlotChip('web ctx',      Colors.orange),
      ),
    ]);
  }
}

class _SlotChip extends StatelessWidget {
  const _SlotChip(this.label, this.color);
  final String label; final Color color;
  @override
  Widget build(BuildContext context) => Chip(
    label: Text('✅ $label', style: TextStyle(fontSize: 13.sp, color: color)),
    backgroundColor: color.withAlpha(20),
    side: BorderSide(color: color),
  );
}

class _SizingApiDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return _Card(children: [
      _Row('ResKit.width(200)',   ResKit.width(200).toStringAsFixed(2)),
      _Row('ResKit.w(200)',       ResKit.w(200).toStringAsFixed(2)),
      _Row('ResKit.height(100)',  ResKit.height(100).toStringAsFixed(2)),
      _Row('ResKit.h(100)',       ResKit.h(100).toStringAsFixed(2)),
      _Row('ResKit.radius(12)',   ResKit.radius(12).toStringAsFixed(2)),
      _Row('ResKit.sp(16)',       ResKit.sp(16).toStringAsFixed(2)),
      _Row('ResKit.sw(0.5)',      ResKit.sw(0.5).toStringAsFixed(2)),
      _Row('ResKit.sh(0.1)',      ResKit.sh(0.1).toStringAsFixed(2)),
      _Row('200.w  (extension)',  200.w.toStringAsFixed(2)),
      _Row('100.h  (extension)',  100.h.toStringAsFixed(2)),
      _Row('12.r   (extension)',  12.r.toStringAsFixed(2)),
      _Row('16.sp  (extension)',  16.sp.toStringAsFixed(2)),
      _Row('0.5.sw (extension)',  0.5.sw.toStringAsFixed(2)),
      _Row('0.1.sh (extension)',  0.1.sh.toStringAsFixed(2)),
      _Row('8.dp   (extension)',  8.dp.toStringAsFixed(2)),
      _Row('screenWidth',         ResKit.screenWidth.toStringAsFixed(1)),
      _Row('screenHeight',        ResKit.screenHeight.toStringAsFixed(1)),
      _Row('statusBarHeight',     ResKit.statusBarHeight.toStringAsFixed(1)),
      _Row('bottomBarHeight',     ResKit.bottomBarHeight.toStringAsFixed(1)),
      _Row('keyboardHeight',      ResKit.keyboardHeight.toStringAsFixed(1)),
      _Row('diagonal',            ResKit.diagonal.toStringAsFixed(1)),
    ]);
  }
}

class _BreakpointDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return _Card(children: [
      _Row('isMobileSmall',  '${ResKit.isMobileSmall}'),
      _Row('isMobile',       '${ResKit.isMobile}'),
      _Row('isMobileLarge',  '${ResKit.isMobileLarge}'),
      _Row('isTabletSmall',  '${ResKit.isTabletSmall}'),
      _Row('isTablet',       '${ResKit.isTablet}'),
      _Row('isTabletLarge',  '${ResKit.isTabletLarge}'),
      _Row('isDesktop',      '${ResKit.isDesktop}'),
      _Row('isDesktopLarge', '${ResKit.isDesktopLarge}'),
      _Row('isDesktopXL',    '${ResKit.isDesktopXL}'),
      _Row('deviceType',     '${ResKit.deviceType}'),
      const Divider(),
      Text(ResKit.adaptive(
            mobile:  '📱 Mobile layout active',
            tablet:  '📟 Tablet layout active',
            desktop: '🖥️ Desktop layout active'),
          style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600)),
    ]);
  }
}

class _PlatformDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return _Card(children: [
      _Row('platform',        '${ResKit.platform}'),
      _Row('isAndroid',       '${ResKit.isAndroid}'),
      _Row('isIOS',           '${ResKit.isIOS}'),
      _Row('isWeb',           '${ResKit.isWeb}'),
      _Row('isMacOS',         '${ResKit.isMacOS}'),
      _Row('isWindows',       '${ResKit.isWindows}'),
      _Row('isLinux',         '${ResKit.isLinux}'),
      _Row('isFuchsia',       '${ResKit.isFuchsia}'),
      _Row('isNativeMobile',  '${ResKit.isNativeMobile}'),
      _Row('isNativeDesktop', '${ResKit.isNativeDesktop}'),
    ]);
  }
}

// ─── Grid demos ──────────────────────────────────────────────────────────────

class _TwoColumnGrid extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GridView.count(
      crossAxisCount: 2,
      shrinkWrap: true,
      physics: const NeverScrollableScrollPhysics(),
      crossAxisSpacing: 12.w,
      mainAxisSpacing: 12.h,
      childAspectRatio: 1.6,
      children: List.generate(4, (i) => _GridCard(i + 1)),
    );
  }
}

class _ThreeColumnGrid extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GridView.count(
      crossAxisCount: 3,
      shrinkWrap: true,
      physics: const NeverScrollableScrollPhysics(),
      crossAxisSpacing: 16.w,
      mainAxisSpacing: 16.h,
      childAspectRatio: 1.8,
      children: List.generate(6, (i) => _GridCard(i + 1)),
    );
  }
}

class _FourColumnGrid extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GridView.count(
      crossAxisCount: 4,
      shrinkWrap: true,
      physics: const NeverScrollableScrollPhysics(),
      crossAxisSpacing: 20.w,
      mainAxisSpacing: 20.h,
      childAspectRatio: 1.5,
      children: List.generate(8, (i) => _GridCard(i + 1)),
    );
  }
}

class _GridCard extends StatelessWidget {
  const _GridCard(this.n);
  final int n;
  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.primaries[n % Colors.primaries.length].withAlpha(30),
        borderRadius: BorderRadius.circular(8.r),
        border: Border.all(
          color: Colors.primaries[n % Colors.primaries.length].withAlpha(80),
        ),
      ),
      alignment: Alignment.center,
      child: Text('Card $n', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600)),
    );
  }
}

class _NavItem extends StatelessWidget {
  const _NavItem({required this.icon, required this.label});
  final IconData icon; final String label;
  @override
  Widget build(BuildContext context) {
    return ListTile(
      leading: Icon(icon, size: 22.r),
      title: Text(label, style: TextStyle(fontSize: 14.sp)),
      dense: true,
    );
  }
}

// ─── Shared UI helpers ───────────────────────────────────────────────────────

class _SectionHeader extends StatelessWidget {
  const _SectionHeader(this.text);
  final String text;
  @override
  Widget build(BuildContext context) => Padding(
    padding: EdgeInsets.only(bottom: 8.h),
    child: Text(text,
        style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w700,
            color: Theme.of(context).colorScheme.primary)),
  );
}

class _Card extends StatelessWidget {
  const _Card({required this.children});
  final List<Widget> children;
  @override
  Widget build(BuildContext context) => Container(
    width: double.infinity,
    padding: EdgeInsets.all(12.r),
    decoration: BoxDecoration(
      color: Theme.of(context).colorScheme.surfaceContainerLowest,
      borderRadius: BorderRadius.circular(8.r),
      border: Border.all(color: Theme.of(context).colorScheme.outlineVariant),
    ),
    child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: children),
  );
}

class _Row extends StatelessWidget {
  const _Row(this.k, this.v);
  final String k, v;
  @override
  Widget build(BuildContext context) => Padding(
    padding: EdgeInsets.symmetric(vertical: 2.h),
    child: Row(children: [
      Expanded(child: Text(k,
          style: TextStyle(fontSize: 11.sp, fontFamily: 'monospace',
              color: Theme.of(context).colorScheme.onSurfaceVariant))),
      Text(v, style: TextStyle(fontSize: 11.sp, fontFamily: 'monospace',
          fontWeight: FontWeight.w600)),
    ]),
  );
}
3
likes
145
points
94
downloads

Documentation

API reference

Publisher

verified publishermysteriouscoder.com

Weekly Downloads

The ultimate Flutter responsive & adaptive toolkit. Combines MediaQuery shortcuts, ScreenUtil-style Figma scaling, LayoutBuilder helpers, platform detection, breakpoint management, and extension-based sizing.

Homepage

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_responsive_plus