empty_view 2.1.1 copy "empty_view: ^2.1.1" to clipboard
empty_view: ^2.1.1 copied to clipboard

A powerful Flutter package for displaying beautiful empty state views with animations, Lottie support, presets, shimmer loading, dark mode, and accessibility features.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:empty_view/empty_view.dart';
import 'package:google_fonts/google_fonts.dart';

void main() {
  // Disable Google Fonts file caching to avoid path_provider issues
  GoogleFonts.config.allowRuntimeFetching = true;
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // Create base text theme with Google Fonts
    final textTheme = GoogleFonts.poppinsTextTheme();

    return MaterialApp(
      title: 'Empty View Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF6366F1),
          brightness: Brightness.light,
        ),
        useMaterial3: true,
        textTheme: textTheme.copyWith(
          headlineLarge: GoogleFonts.plusJakartaSans(
            fontSize: 32,
            fontWeight: FontWeight.w800,
            letterSpacing: -0.5,
          ),
          headlineMedium: GoogleFonts.plusJakartaSans(
            fontSize: 28,
            fontWeight: FontWeight.w700,
            letterSpacing: -0.3,
          ),
          titleLarge: GoogleFonts.plusJakartaSans(
            fontSize: 22,
            fontWeight: FontWeight.w600,
          ),
          titleMedium: GoogleFonts.poppins(
            fontSize: 16,
            fontWeight: FontWeight.w600,
          ),
          bodyLarge: GoogleFonts.poppins(
            fontSize: 16,
            fontWeight: FontWeight.w400,
          ),
          bodyMedium: GoogleFonts.poppins(
            fontSize: 14,
            fontWeight: FontWeight.w400,
          ),
          labelLarge: GoogleFonts.poppins(
            fontSize: 14,
            fontWeight: FontWeight.w600,
          ),
        ),
        appBarTheme: AppBarTheme(
          centerTitle: true,
          elevation: 0,
          titleTextStyle: GoogleFonts.plusJakartaSans(
            fontSize: 18,
            fontWeight: FontWeight.w600,
            color: Colors.black87,
          ),
        ),
      ),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF6366F1),
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
        textTheme: textTheme.copyWith(
          headlineLarge: GoogleFonts.plusJakartaSans(
            fontSize: 32,
            fontWeight: FontWeight.w800,
            letterSpacing: -0.5,
            color: Colors.white,
          ),
          headlineMedium: GoogleFonts.plusJakartaSans(
            fontSize: 28,
            fontWeight: FontWeight.w700,
            letterSpacing: -0.3,
            color: Colors.white,
          ),
          titleLarge: GoogleFonts.plusJakartaSans(
            fontSize: 22,
            fontWeight: FontWeight.w600,
            color: Colors.white,
          ),
          titleMedium: GoogleFonts.poppins(
            fontSize: 16,
            fontWeight: FontWeight.w600,
            color: Colors.white,
          ),
          bodyLarge: GoogleFonts.poppins(
            fontSize: 16,
            fontWeight: FontWeight.w400,
            color: Colors.white70,
          ),
          bodyMedium: GoogleFonts.poppins(
            fontSize: 14,
            fontWeight: FontWeight.w400,
            color: Colors.white70,
          ),
          labelLarge: GoogleFonts.poppins(
            fontSize: 14,
            fontWeight: FontWeight.w600,
            color: Colors.white,
          ),
        ),
        appBarTheme: AppBarTheme(
          centerTitle: true,
          elevation: 0,
          titleTextStyle: GoogleFonts.plusJakartaSans(
            fontSize: 18,
            fontWeight: FontWeight.w600,
            color: Colors.white,
          ),
        ),
      ),
      themeMode: ThemeMode.system,
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return Scaffold(
      body: SafeArea(
        child: CustomScrollView(
          slivers: [
            SliverToBoxAdapter(
              child: Padding(
                padding: const EdgeInsets.fromLTRB(24, 40, 24, 32),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 12,
                        vertical: 6,
                      ),
                      decoration: BoxDecoration(
                        gradient: LinearGradient(
                          colors: [
                            theme.colorScheme.primary,
                            theme.colorScheme.secondary,
                          ],
                        ),
                        borderRadius: BorderRadius.circular(20),
                        boxShadow: [
                          BoxShadow(
                            color: theme.colorScheme.primary.withValues(alpha: 0.3),
                            blurRadius: 8,
                            offset: const Offset(0, 2),
                          ),
                        ],
                      ),
                      child: Text(
                        'v2.1.0',
                        style: GoogleFonts.spaceMono(
                          fontSize: 12,
                          fontWeight: FontWeight.w700,
                          color: Colors.white,
                          letterSpacing: 1,
                        ),
                      ),
                    ),
                    const SizedBox(height: 20),
                    ShaderMask(
                      shaderCallback: (bounds) => LinearGradient(
                        colors: [
                          theme.colorScheme.primary,
                          theme.colorScheme.tertiary,
                        ],
                      ).createShader(bounds),
                      child: Text(
                        'Empty View',
                        style: GoogleFonts.plusJakartaSans(
                          fontSize: 36,
                          fontWeight: FontWeight.w800,
                          letterSpacing: -1,
                          color: Colors.white,
                        ),
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      'Beautiful empty states for Flutter apps',
                      style: GoogleFonts.poppins(
                        fontSize: 16,
                        fontWeight: FontWeight.w400,
                        color: theme.colorScheme.onSurfaceVariant,
                        height: 1.5,
                      ),
                    ),
                    const SizedBox(height: 24),
                    // Demo GIF showcase
                    ClipRRect(
                      borderRadius: BorderRadius.circular(20),
                      child: Container(
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(20),
                          boxShadow: [
                            BoxShadow(
                              color: theme.colorScheme.primary.withValues(alpha: 0.2),
                              blurRadius: 20,
                              offset: const Offset(0, 10),
                            ),
                          ],
                        ),
                        child: Image.asset(
                          'assets/demo.gif',
                          width: double.infinity,
                          fit: BoxFit.cover,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            SliverPadding(
              padding: const EdgeInsets.symmetric(horizontal: 16),
              sliver: SliverGrid(
                gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2,
                  mainAxisSpacing: 12,
                  crossAxisSpacing: 12,
                  childAspectRatio: 1.1,
                ),
                delegate: SliverChildListDelegate([
                  _FeatureCard(
                    icon: Icons.auto_awesome,
                    title: 'Presets',
                    subtitle: '16 ready-to-use',
                    color: Colors.purple,
                    onTap: () => _navigate(context, const PresetsDemo()),
                  ),
                  _FeatureCard(
                    icon: Icons.animation,
                    title: 'Animations',
                    subtitle: '5 entrance effects',
                    color: Colors.blue,
                    onTap: () => _navigate(context, const AnimationsDemo()),
                  ),
                  _FeatureCard(
                    icon: Icons.gradient,
                    title: 'Gradients',
                    subtitle: 'NEW in v2.1',
                    color: Colors.deepPurple,
                    onTap: () => _navigate(context, const GradientDemo()),
                  ),
                  _FeatureCard(
                    icon: Icons.build_circle,
                    title: 'Builder',
                    subtitle: 'NEW in v2.1',
                    color: Colors.cyan,
                    onTap: () => _navigate(context, const BuilderDemo()),
                  ),
                  _FeatureCard(
                    icon: Icons.play_circle_outline,
                    title: 'Lottie',
                    subtitle: 'Animated graphics',
                    color: Colors.orange,
                    onTap: () => _navigate(context, const LottieDemo()),
                  ),
                  _FeatureCard(
                    icon: Icons.blur_on,
                    title: 'Shimmer',
                    subtitle: 'Skeleton loading',
                    color: Colors.teal,
                    onTap: () => _navigate(context, const ShimmerDemo()),
                  ),
                  _FeatureCard(
                    icon: Icons.category,
                    title: 'Icons',
                    subtitle: 'Material icons',
                    color: Colors.pink,
                    onTap: () => _navigate(context, const IconsDemo()),
                  ),
                  _FeatureCard(
                    icon: Icons.palette,
                    title: 'Styling',
                    subtitle: 'Custom themes',
                    color: Colors.indigo,
                    onTap: () => _navigate(context, const StylingDemo()),
                  ),
                  _FeatureCard(
                    icon: Icons.cases_rounded,
                    title: 'Real World',
                    subtitle: 'Live examples',
                    color: Colors.green,
                    onTap: () => _navigate(context, const RealWorldDemo()),
                  ),
                ]),
              ),
            ),
            const SliverToBoxAdapter(child: SizedBox(height: 32)),
          ],
        ),
      ),
    );
  }

  void _navigate(BuildContext context, Widget page) {
    Navigator.push(
      context,
      MaterialPageRoute(builder: (_) => page),
    );
  }
}

class _FeatureCard extends StatelessWidget {
  final IconData icon;
  final String title;
  final String subtitle;
  final Color color;
  final VoidCallback onTap;

  const _FeatureCard({
    required this.icon,
    required this.title,
    required this.subtitle,
    required this.color,
    required this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final isDark = theme.brightness == Brightness.dark;

    return Material(
      color: isDark ? theme.colorScheme.surfaceContainerHighest : Colors.white,
      borderRadius: BorderRadius.circular(16),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(16),
        child: Container(
          padding: const EdgeInsets.all(16),
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(16),
            border: Border.all(
              color: theme.colorScheme.outlineVariant.withValues(alpha: 0.5),
            ),
          ),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Container(
                padding: const EdgeInsets.all(10),
                decoration: BoxDecoration(
                  color: color.withValues(alpha: 0.1),
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Icon(icon, color: color, size: 24),
              ),
              const Spacer(),
              Text(
                title,
                style: GoogleFonts.plusJakartaSans(
                  fontSize: 15,
                  fontWeight: FontWeight.w700,
                  letterSpacing: -0.2,
                ),
              ),
              const SizedBox(height: 4),
              Text(
                subtitle,
                style: GoogleFonts.poppins(
                  fontSize: 12,
                  fontWeight: FontWeight.w400,
                  color: theme.colorScheme.onSurfaceVariant,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// ============ PRESETS DEMO ============
class PresetsDemo extends StatefulWidget {
  const PresetsDemo({super.key});

  @override
  State<PresetsDemo> createState() => _PresetsDemoState();
}

class _PresetsDemoState extends State<PresetsDemo> {
  final PageController _controller = PageController();
  int _currentPage = 0;

  final List<_PresetItem> _presets = [
    _PresetItem('No Internet', Icons.wifi_off, Colors.grey),
    _PresetItem('Empty Cart', Icons.shopping_cart, Colors.orange),
    _PresetItem('No Results', Icons.search_off, Colors.blue),
    _PresetItem('Error', Icons.error_outline, Colors.red),
    _PresetItem('No Messages', Icons.message, Colors.green),
    _PresetItem('No Favorites', Icons.favorite, Colors.pink),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Presets')),
      body: Column(
        children: [
          Expanded(
            child: PageView(
              controller: _controller,
              onPageChanged: (i) => setState(() => _currentPage = i),
              children: [
                EmptyViewPresets.noInternet(onRetry: () {}),
                EmptyViewPresets.emptyCart(onShopNow: () {}),
                EmptyViewPresets.noSearchResults(searchTerm: 'flutter'),
                EmptyViewPresets.error(message: 'Failed to load data'),
                EmptyViewPresets.noMessages(onNewMessage: () {}),
                EmptyViewPresets.noFavorites(onBrowse: () {}),
              ],
            ),
          ),
          SafeArea(
            child: Padding(
              padding: const EdgeInsets.all(24),
              child: Column(
                children: [
                  Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: List.generate(
                      _presets.length,
                      (i) => Container(
                        width: 8,
                        height: 8,
                        margin: const EdgeInsets.symmetric(horizontal: 4),
                        decoration: BoxDecoration(
                          shape: BoxShape.circle,
                          color: _currentPage == i
                              ? Theme.of(context).colorScheme.primary
                              : Theme.of(context).colorScheme.outlineVariant,
                        ),
                      ),
                    ),
                  ),
                  const SizedBox(height: 16),
                  Text(
                    _presets[_currentPage].name,
                    style: GoogleFonts.plusJakartaSans(
                      fontSize: 18,
                      fontWeight: FontWeight.w700,
                      letterSpacing: -0.3,
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _PresetItem {
  final String name;
  final IconData icon;
  final Color color;
  _PresetItem(this.name, this.icon, this.color);
}

// ============ ANIMATIONS DEMO ============
class AnimationsDemo extends StatefulWidget {
  const AnimationsDemo({super.key});

  @override
  State<AnimationsDemo> createState() => _AnimationsDemoState();
}

class _AnimationsDemoState extends State<AnimationsDemo> {
  EmptyViewAnimation _selectedAnimation = EmptyViewAnimation.fadeIn;
  int _key = 0;

  final List<_AnimationOption> _animations = [
    _AnimationOption('Fade In', EmptyViewAnimation.fadeIn),
    _AnimationOption('Slide Up', EmptyViewAnimation.slideUp),
    _AnimationOption('Scale', EmptyViewAnimation.scale),
    _AnimationOption('Bounce', EmptyViewAnimation.bounce),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Animations')),
      body: Column(
        children: [
          Expanded(
            child: EmptyView(
              key: ValueKey(_key),
              icon: Icons.animation,
              iconColor: Colors.purple,
              iconSize: 80,
              showIconBackground: true,
              iconBackgroundColor: Colors.purple.withValues(alpha: 0.1),
              title: 'Smooth Animations',
              description: 'Beautiful entrance animations for your empty states',
              animationType: _selectedAnimation,
              animationDuration: const Duration(milliseconds: 800),
            ),
          ),
          SafeArea(
            child: Container(
              padding: const EdgeInsets.all(20),
              child: Wrap(
                spacing: 8,
                runSpacing: 8,
                alignment: WrapAlignment.center,
                children: _animations.map((anim) {
                  final isSelected = _selectedAnimation == anim.type;
                  return ChoiceChip(
                    label: Text(anim.name),
                    selected: isSelected,
                    onSelected: (_) {
                      setState(() {
                        _selectedAnimation = anim.type;
                        _key++;
                      });
                    },
                  );
                }).toList(),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _AnimationOption {
  final String name;
  final EmptyViewAnimation type;
  _AnimationOption(this.name, this.type);
}

// ============ LOTTIE DEMO ============
class LottieDemo extends StatelessWidget {
  const LottieDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Lottie Animation')),
      body: EmptyView(
        lottiePath: 'https://assets10.lottiefiles.com/packages/lf20_ysrn2iwp.json',
        lottieRepeat: true,
        title: 'Lottie Support',
        description: 'Display beautiful animated graphics from Lottie files or network URLs',
        buttonText: 'Learn More',
        onButtonTap: () {},
        animationType: EmptyViewAnimation.fadeIn,
        style: const EmptyViewStyle(
          imageWidthFactor: 0.6,
          imageHeightFactor: 0.3,
        ),
      ),
    );
  }
}

// ============ SHIMMER DEMO ============
class ShimmerDemo extends StatefulWidget {
  const ShimmerDemo({super.key});

  @override
  State<ShimmerDemo> createState() => _ShimmerDemoState();
}

class _ShimmerDemoState extends State<ShimmerDemo> {
  bool _isLoading = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Shimmer Loading')),
      body: Column(
        children: [
          Expanded(
            child: _isLoading
                ? EmptyView.skeleton()
                : EmptyView(
                    icon: Icons.check_circle,
                    iconColor: Colors.green,
                    iconSize: 80,
                    showIconBackground: true,
                    iconBackgroundColor: Colors.green.withValues(alpha: 0.1),
                    title: 'Content Loaded',
                    description: 'The shimmer skeleton is replaced with actual content',
                    animationType: EmptyViewAnimation.fadeIn,
                  ),
          ),
          SafeArea(
            child: Padding(
              padding: const EdgeInsets.all(24),
              child: SizedBox(
                width: double.infinity,
                child: FilledButton.icon(
                  onPressed: () => setState(() => _isLoading = !_isLoading),
                  icon: Icon(_isLoading ? Icons.visibility : Icons.refresh),
                  label: Text(_isLoading ? 'Show Content' : 'Show Shimmer'),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

// ============ ICONS DEMO ============
class IconsDemo extends StatelessWidget {
  const IconsDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Icon Support')),
      body: PageView(
        children: [
          EmptyView(
            icon: Icons.cloud_off_rounded,
            iconColor: Colors.blue,
            iconSize: 80,
            showIconBackground: true,
            iconBackgroundColor: Colors.blue.withValues(alpha: 0.1),
            title: 'Icon with Background',
            description: 'Icons can be displayed with a circular background container',
            animationType: EmptyViewAnimation.scale,
          ),
          EmptyView(
            icon: Icons.inbox_rounded,
            iconColor: Colors.grey,
            iconSize: 80,
            title: 'Simple Icon',
            description: 'Or display icons without any background',
            animationType: EmptyViewAnimation.fadeIn,
          ),
        ],
      ),
    );
  }
}

// ============ STYLING DEMO ============
class StylingDemo extends StatelessWidget {
  const StylingDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Custom Styling')),
      body: EmptyView(
        icon: Icons.palette_rounded,
        iconColor: Colors.indigo,
        iconSize: 80,
        showIconBackground: true,
        iconBackgroundColor: Colors.indigo.withValues(alpha: 0.1),
        title: 'Fully Customizable',
        description: 'Customize colors, fonts, spacing, and more with EmptyViewStyle',
        buttonText: 'Primary Action',
        onButtonTap: () {},
        secondaryButtonText: 'Secondary Action',
        onSecondaryButtonTap: () {},
        animationType: EmptyViewAnimation.slideUp,
        style: EmptyViewStyle(
          titleStyle: GoogleFonts.plusJakartaSans(
            fontSize: 26,
            fontWeight: FontWeight.w800,
            color: Colors.indigo,
            letterSpacing: -0.5,
          ),
          descriptionStyle: GoogleFonts.poppins(
            fontSize: 15,
            fontWeight: FontWeight.w400,
            color: Colors.grey.shade600,
            height: 1.5,
          ),
          buttonTextStyle: GoogleFonts.poppins(
            fontSize: 16,
            fontWeight: FontWeight.w600,
          ),
          buttonColor: Colors.indigo,
          buttonBorderRadius: 25,
          buttonHeight: 52,
          secondaryButtonColor: Colors.indigo,
        ),
      ),
    );
  }
}

// ============ GRADIENT DEMO (NEW in v2.1) ============
class GradientDemo extends StatefulWidget {
  const GradientDemo({super.key});

  @override
  State<GradientDemo> createState() => _GradientDemoState();
}

class _GradientDemoState extends State<GradientDemo> {
  int _currentGradient = 0;

  final List<_GradientOption> _gradients = [
    _GradientOption(
      'Sunset',
      LinearGradient(
        begin: Alignment.topLeft,
        end: Alignment.bottomRight,
        colors: [Colors.orange.shade100, Colors.pink.shade100],
      ),
    ),
    _GradientOption(
      'Ocean',
      LinearGradient(
        begin: Alignment.topCenter,
        end: Alignment.bottomCenter,
        colors: [Colors.blue.shade100, Colors.cyan.shade50],
      ),
    ),
    _GradientOption(
      'Forest',
      LinearGradient(
        begin: Alignment.topLeft,
        end: Alignment.bottomRight,
        colors: [Colors.green.shade100, Colors.teal.shade50],
      ),
    ),
    _GradientOption(
      'Lavender',
      LinearGradient(
        begin: Alignment.topCenter,
        end: Alignment.bottomCenter,
        colors: [Colors.purple.shade100, Colors.indigo.shade50],
      ),
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Gradient Backgrounds')),
      body: Column(
        children: [
          Expanded(
            child: EmptyView(
              key: ValueKey(_currentGradient),
              icon: Icons.gradient,
              iconColor: Colors.deepPurple,
              iconSize: 80,
              showIconBackground: true,
              iconBackgroundColor: Colors.white,
              title: 'Beautiful Gradients',
              description: 'Add stunning gradient backgrounds to your empty states',
              buttonText: 'Get Started',
              onButtonTap: () {},
              animationType: EmptyViewAnimation.fadeIn,
              style: EmptyViewStyle.gradient(
                gradient: _gradients[_currentGradient].gradient,
                borderRadius: BorderRadius.circular(24),
                buttonColor: Colors.deepPurple,
              ),
            ),
          ),
          SafeArea(
            child: Container(
              padding: const EdgeInsets.all(20),
              child: Wrap(
                spacing: 8,
                runSpacing: 8,
                alignment: WrapAlignment.center,
                children: List.generate(_gradients.length, (i) {
                  return ChoiceChip(
                    label: Text(_gradients[i].name),
                    selected: _currentGradient == i,
                    onSelected: (_) => setState(() => _currentGradient = i),
                  );
                }),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _GradientOption {
  final String name;
  final Gradient gradient;
  _GradientOption(this.name, this.gradient);
}

// ============ BUILDER DEMO (NEW in v2.1) ============
class BuilderDemo extends StatefulWidget {
  const BuilderDemo({super.key});

  @override
  State<BuilderDemo> createState() => _BuilderDemoState();
}

class _BuilderDemoState extends State<BuilderDemo> {
  bool _animationComplete = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Builder Pattern')),
      body: EmptyView.builder(
        description: 'Create fully custom layouts with the builder pattern',
        title: 'Custom Layout',
        builder: (context, style) => Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            // Custom animated icon container
            TweenAnimationBuilder<double>(
              tween: Tween(begin: 0.0, end: 1.0),
              duration: const Duration(milliseconds: 800),
              builder: (context, value, child) => Transform.scale(
                scale: value,
                child: child,
              ),
              child: Container(
                width: 120,
                height: 120,
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    begin: Alignment.topLeft,
                    end: Alignment.bottomRight,
                    colors: [Colors.cyan.shade400, Colors.blue.shade600],
                  ),
                  shape: BoxShape.circle,
                  boxShadow: [
                    BoxShadow(
                      color: Colors.blue.withValues(alpha: 0.3),
                      blurRadius: 20,
                      offset: const Offset(0, 10),
                    ),
                  ],
                ),
                child: const Icon(
                  Icons.rocket_launch_rounded,
                  size: 56,
                  color: Colors.white,
                ),
              ),
            ),
            const SizedBox(height: 24),
            // Custom status indicator
            if (_animationComplete)
              Container(
                padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
                decoration: BoxDecoration(
                  color: Colors.green.shade100,
                  borderRadius: BorderRadius.circular(20),
                ),
                child: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Icon(Icons.check_circle, size: 16, color: Colors.green.shade700),
                    const SizedBox(width: 6),
                    Text(
                      'Animation Complete!',
                      style: GoogleFonts.poppins(
                        color: Colors.green.shade700,
                        fontWeight: FontWeight.w600,
                        fontSize: 12,
                      ),
                    ),
                  ],
                ),
              ),
          ],
        ),
        buttonText: 'Launch App',
        onButtonTap: () {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Launching...')),
          );
        },
        secondaryButtonText: 'Learn More',
        onSecondaryButtonTap: () {},
        onAnimationComplete: () {
          setState(() => _animationComplete = true);
        },
        animationType: EmptyViewAnimation.slideUp,
        style: EmptyViewStyle(
          titleStyle: GoogleFonts.plusJakartaSans(
            fontSize: 24,
            fontWeight: FontWeight.w800,
            letterSpacing: -0.5,
          ),
          descriptionStyle: GoogleFonts.poppins(
            fontSize: 15,
            color: Colors.grey.shade600,
            height: 1.5,
          ),
          buttonTextStyle: GoogleFonts.poppins(
            fontSize: 16,
            fontWeight: FontWeight.w600,
          ),
          buttonColor: Colors.blue.shade600,
          buttonBorderRadius: 25,
        ),
      ),
    );
  }
}

// ============ REAL WORLD DEMO ============
class RealWorldDemo extends StatelessWidget {
  const RealWorldDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 5,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Real World Examples'),
          bottom: const TabBar(
            isScrollable: true,
            tabs: [
              Tab(text: 'E-Commerce'),
              Tab(text: 'Social'),
              Tab(text: 'Finance'),
              Tab(text: 'Productivity'),
              Tab(text: 'Auth'),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            // E-Commerce: Empty Cart
            _buildEcommerceExample(),
            // Social: Empty Feed
            _buildSocialExample(),
            // Finance: No Transactions
            _buildFinanceExample(),
            // Productivity: No Tasks
            _buildProductivityExample(),
            // Auth: Session Expired
            _buildAuthExample(),
          ],
        ),
      ),
    );
  }

  Widget _buildEcommerceExample() {
    return EmptyView(
      icon: Icons.shopping_bag_outlined,
      iconColor: Colors.orange,
      iconSize: 100,
      showIconBackground: true,
      iconBackgroundColor: Colors.orange.shade50,
      title: 'Your Cart is Empty',
      description: 'Looks like you haven\'t added anything to your cart yet. Start shopping to fill it up!',
      buttonText: 'Browse Products',
      onButtonTap: () {},
      secondaryButtonText: 'View Wishlist',
      onSecondaryButtonTap: () {},
      animationType: EmptyViewAnimation.scale,
      style: EmptyViewStyle(
        titleStyle: GoogleFonts.plusJakartaSans(
          fontSize: 24,
          fontWeight: FontWeight.w800,
          letterSpacing: -0.5,
        ),
        descriptionStyle: GoogleFonts.poppins(
          fontSize: 15,
          color: Colors.grey.shade600,
          height: 1.5,
        ),
        buttonTextStyle: GoogleFonts.poppins(
          fontSize: 16,
          fontWeight: FontWeight.w600,
        ),
        buttonColor: Colors.orange,
        buttonBorderRadius: 12,
        secondaryButtonColor: Colors.orange,
      ),
    );
  }

  Widget _buildSocialExample() {
    return EmptyView.builder(
      description: 'Follow some people to see their posts in your feed',
      title: 'Your Feed is Empty',
      builder: (context, style) => Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Stack(
            alignment: Alignment.center,
            children: [
              Container(
                width: 100,
                height: 100,
                decoration: BoxDecoration(
                  color: Colors.blue.shade50,
                  shape: BoxShape.circle,
                ),
              ),
              const Icon(
                Icons.people_outline_rounded,
                size: 60,
                color: Colors.blue,
              ),
            ],
          ),
          const SizedBox(height: 16),
          // Suggested users
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: List.generate(3, (i) => Container(
              margin: const EdgeInsets.symmetric(horizontal: 4),
              width: 40,
              height: 40,
              decoration: BoxDecoration(
                color: Colors.grey.shade200,
                shape: BoxShape.circle,
                border: Border.all(color: Colors.white, width: 2),
              ),
              child: Icon(Icons.person, size: 24, color: Colors.grey.shade400),
            )),
          ),
        ],
      ),
      buttonText: 'Find Friends',
      onButtonTap: () {},
      animationType: EmptyViewAnimation.fadeIn,
      style: EmptyViewStyle(
        titleStyle: GoogleFonts.plusJakartaSans(
          fontSize: 24,
          fontWeight: FontWeight.w800,
          letterSpacing: -0.5,
        ),
        descriptionStyle: GoogleFonts.poppins(
          fontSize: 15,
          color: Colors.grey.shade600,
          height: 1.5,
        ),
        buttonTextStyle: GoogleFonts.poppins(
          fontSize: 16,
          fontWeight: FontWeight.w600,
        ),
        buttonColor: Colors.blue,
        buttonBorderRadius: 25,
      ),
    );
  }

  Widget _buildFinanceExample() {
    return EmptyView(
      icon: Icons.account_balance_wallet_outlined,
      iconColor: Colors.green.shade700,
      iconSize: 90,
      showIconBackground: true,
      iconBackgroundColor: Colors.green.shade50,
      title: 'No Transactions Yet',
      description: 'Your transaction history will appear here once you make your first payment or transfer.',
      buttonText: 'Add Money',
      onButtonTap: () {},
      secondaryButtonText: 'Link Bank Account',
      onSecondaryButtonTap: () {},
      animationType: EmptyViewAnimation.slideUp,
      style: EmptyViewStyle(
        backgroundGradient: LinearGradient(
          begin: Alignment.topCenter,
          end: Alignment.bottomCenter,
          colors: [Colors.green.shade50, Colors.white],
        ),
        titleStyle: GoogleFonts.plusJakartaSans(
          fontSize: 24,
          fontWeight: FontWeight.w800,
          letterSpacing: -0.5,
        ),
        descriptionStyle: GoogleFonts.poppins(
          fontSize: 15,
          color: Colors.grey.shade600,
          height: 1.5,
        ),
        buttonTextStyle: GoogleFonts.poppins(
          fontSize: 16,
          fontWeight: FontWeight.w600,
        ),
        buttonColor: Colors.green.shade700,
        buttonBorderRadius: 12,
      ),
    );
  }

  Widget _buildProductivityExample() {
    return EmptyView(
      icon: Icons.task_alt_rounded,
      iconColor: Colors.purple,
      iconSize: 90,
      showIconBackground: true,
      iconBackgroundColor: Colors.purple.shade50,
      title: 'All Caught Up!',
      description: 'You\'ve completed all your tasks. Take a break or add new tasks to stay productive.',
      buttonText: 'Add New Task',
      onButtonTap: () {},
      animationType: EmptyViewAnimation.bounce,
      style: EmptyViewStyle(
        titleStyle: GoogleFonts.plusJakartaSans(
          fontSize: 26,
          fontWeight: FontWeight.w800,
          color: Colors.purple.shade700,
          letterSpacing: -0.5,
        ),
        descriptionStyle: GoogleFonts.poppins(
          fontSize: 15,
          color: Colors.grey.shade600,
          height: 1.5,
        ),
        buttonTextStyle: GoogleFonts.poppins(
          fontSize: 16,
          fontWeight: FontWeight.w600,
        ),
        buttonColor: Colors.purple,
        buttonBorderRadius: 16,
      ),
    );
  }

  Widget _buildAuthExample() {
    return EmptyViewPresets.sessionExpired(
      onLogin: () {},
      title: 'Session Expired',
      description: 'Your session has timed out for security reasons. Please log in again to continue.',
      style: EmptyViewStyle(
        titleStyle: GoogleFonts.plusJakartaSans(
          fontSize: 24,
          fontWeight: FontWeight.w800,
          letterSpacing: -0.5,
        ),
        descriptionStyle: GoogleFonts.poppins(
          fontSize: 15,
          color: Colors.grey.shade600,
          height: 1.5,
        ),
        buttonTextStyle: GoogleFonts.poppins(
          fontSize: 16,
          fontWeight: FontWeight.w600,
        ),
        buttonColor: Colors.red.shade400,
        buttonBorderRadius: 12,
      ),
    );
  }
}
3
likes
160
points
107
downloads

Publisher

unverified uploader

Weekly Downloads

A powerful Flutter package for displaying beautiful empty state views with animations, Lottie support, presets, shimmer loading, dark mode, and accessibility features.

Repository (GitHub)
View/report issues

Topics

#widget #ui #empty-state #placeholder #animation

Documentation

API reference

License

MIT (license)

Dependencies

flutter, flutter_svg, lottie, shimmer

More

Packages that depend on empty_view