smart_state_handler 1.0.2 copy "smart_state_handler: ^1.0.2" to clipboard
smart_state_handler: ^1.0.2 copied to clipboard

A comprehensive Flutter widget for handling all UI states (loading, error, success, empty, offline) with animations, overlay mode, pagination, and extensive customization options.

example/lib/main.dart

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

/// Complete runnable example for SmartStateHandler package
///
/// This example demonstrates ALL FEATURES including v1.0.2 updates:
/// ✨ Dismissible overlays with custom styling
/// 🎯 Top/bottom positioned snackbars with actions
/// 🎨 Per-state overlay customization
/// 📱 Selective overlay states (show overlay only for specific states)
/// 🔄 Pull-to-refresh with smart configurations
/// 📄 Pagination with debouncing (300ms)
/// 🎭 Multiple animation transitions
/// 💾 SmartStateCache for data caching
/// ⚡ SmartStateMemoization for performance
/// 🔄 SmartStateKeepAlive for list optimization
/// 💡 Smart defaults with easy customization
void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SmartStateHandler Example',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const SmartStateHandlerExample(),
    );
  }
}

/// Example demonstrating enhanced SmartStateHandler features
/// Shows animations, memoization, custom icons, and improved configurations
/// NOW WITH v1.0.2 Performance Features:
/// - SmartStateCache for efficient data caching
/// - SmartStateMemoization for preventing rebuilds
/// - SmartStateKeepAlive for list state preservation

class SmartStateHandlerExample extends StatefulWidget {
  const SmartStateHandlerExample({super.key});

  @override
  State<SmartStateHandlerExample> createState() =>
      _SmartStateHandlerExampleState();
}

class _SmartStateHandlerExampleState extends State<SmartStateHandlerExample>
    with SmartStateMemoization {
  SmartState _currentState = SmartState.initial;
  List<String> _data = [];
  String? _error;
  bool _hasMoreData = true;
  bool _enableOverlayMode = false;
  bool _enableSnackbar = false;
  bool _snackbarAtTop = true;
  bool _dismissibleOverlay = true;
  SmartStateTransitionType _transitionType = SmartStateTransitionType.fade;
  SmartStateTransitionType _overlayTransitionType =
      SmartStateTransitionType.scale;

  // Form-related state for overlay demo
  final _formKey = GlobalKey<FormState>();
  final _nameController = TextEditingController();
  final _emailController = TextEditingController();
  final _scrollController = ScrollController();

  // 💾 NEW v1.0.2: Cache utility for efficient data management
  final _dataCache = SmartStateCache<String, List<String>>(maxSize: 50);

  @override
  void initState() {
    super.initState();
    // Start with initial state - user can trigger loading manually

    // Check if we have cached data
    final cachedData = _dataCache.get('main_data');
    if (cachedData != null) {
      setState(() {
        _data = cachedData;
        _currentState = SmartState.success;
      });
    }
  }

  @override
  void dispose() {
    _nameController.dispose();
    _emailController.dispose();
    _scrollController.dispose();
    super.dispose();
  }

  /// Simulate form submission with different outcomes
  Future<void> _submitForm() async {
    if (_formKey.currentState?.validate() ?? false) {
      setState(() {
        _currentState = SmartState.loading;
        _error = null;
      });

      await Future.delayed(const Duration(seconds: 2));

      // Simulate different outcomes
      final outcomes = [
        () => setState(() => _currentState = SmartState.success),
        () => setState(() {
              _currentState = SmartState.error;
              _error = 'Form submission failed. Please try again.';
            }),
      ];

      final randomIndex = DateTime.now().millisecond % outcomes.length;
      outcomes[randomIndex]();

      // Auto-dismiss success after 3 seconds
      if (_currentState.isSuccess) {
        Future.delayed(const Duration(seconds: 3), () {
          if (mounted && _currentState.isSuccess) {
            setState(() => _currentState = SmartState.initial);
          }
        });
      }
    }
  }

  /// Simulate API call with different outcomes
  Future<void> _simulateDataLoading({
    bool isRefresh = false,
    bool isLoadMore = false,
  }) async {
    if (!isLoadMore) {
      setState(() {
        _currentState = SmartState.loading;
        if (isRefresh) {
          _data.clear();
          _hasMoreData = true;
        }
        _error = null;
      });
    }

    await Future.delayed(const Duration(seconds: 2));

    // Simulate different scenarios randomly
    final scenarios = [
      () => _simulateSuccess(),
      () => _simulateError(),
      () => _simulateEmpty(),
      () => _simulateOffline(),
    ];

    final randomIndex = DateTime.now().millisecond % scenarios.length;
    scenarios[randomIndex]();
  }

  void _simulateSuccess() {
    setState(() {
      _data = List.generate(10, (index) => 'Item ${index + 1}');
      _currentState = SmartState.success;
      _error = null;

      // 💾 NEW v1.0.2: Cache the data
      _dataCache.put('main_data', _data);
    });
  }

  void _simulateError() {
    setState(() {
      _currentState = SmartState.error;
      _error = 'Failed to fetch data from server';
    });
  }

  void _simulateEmpty() {
    setState(() {
      _data = [];
      _currentState = SmartState.empty;
      _error = null;
    });
  }

  void _simulateOffline() {
    setState(() {
      _currentState = SmartState.offline;
      _error = null;
    });
  }

  /// Simulate loading more data for pagination
  Future<void> _loadMoreData() async {
    if (!_hasMoreData) return;

    setState(() {
      _currentState = SmartState.loadingMore;
    });

    await Future.delayed(const Duration(seconds: 1));

    setState(() {
      final newItems = List.generate(
        5,
        (index) => 'Item ${_data.length + index + 1}',
      );
      _data.addAll(newItems);
      _currentState = SmartState.success;

      // 💾 NEW v1.0.2: Update cache with new data
      _dataCache.put('main_data', _data);

      // Simulate end of data after 20 items
      if (_data.length >= 20) {
        _hasMoreData = false;
      }
    });
  }

  /// Show control panel in a modal bottom sheet
  void _showControlPanel(BuildContext context, VoidCallback onStateChanged) {
    showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      backgroundColor: Colors.transparent,
      builder: (context) => DraggableScrollableSheet(
        initialChildSize: 0.8,
        minChildSize: 0.5,
        maxChildSize: 0.95,
        builder: (context, scrollController) => StatefulBuilder(
          builder: (context, setState) => Container(
            decoration: const BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
            ),
            child: Column(
              children: [
                // Handle
                Container(
                  margin: const EdgeInsets.only(top: 8),
                  width: 40,
                  height: 4,
                  decoration: BoxDecoration(
                    color: Colors.grey[300],
                    borderRadius: BorderRadius.circular(2),
                  ),
                ),
                // Header
                Container(
                  padding: const EdgeInsets.all(16),
                  decoration: const BoxDecoration(
                    border: Border(
                        bottom: BorderSide(color: Colors.grey, width: 0.5)),
                  ),
                  child: Row(
                    children: [
                      const Icon(Icons.settings, color: Colors.deepPurple),
                      const SizedBox(width: 12),
                      const Text(
                        'Demo Controls',
                        style: TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const Spacer(),
                      IconButton(
                        icon: const Icon(Icons.close),
                        onPressed: () => Navigator.of(context).pop(),
                      ),
                    ],
                  ),
                ),
                // Content
                Expanded(
                  child: SingleChildScrollView(
                    controller: scrollController,
                    padding: const EdgeInsets.all(16),
                    child: // Mode toggle section
                        Container(
                      padding: const EdgeInsets.all(12),
                      decoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius: BorderRadius.circular(8),
                        border: Border.all(color: Colors.grey[300]!),
                      ),
                      child: Column(
                        children: [
                          Row(
                            children: [
                              Icon(
                                _enableOverlayMode
                                    ? Icons.layers
                                    : Icons.view_stream,
                                color: Theme.of(context).primaryColor,
                              ),
                              const SizedBox(width: 8),
                              Expanded(
                                child: Column(
                                  crossAxisAlignment: CrossAxisAlignment.start,
                                  children: [
                                    Text(
                                      _enableOverlayMode
                                          ? 'Overlay Mode (Form Demo)'
                                          : 'Normal Mode (List Demo)',
                                      style: const TextStyle(
                                          fontWeight: FontWeight.bold),
                                    ),
                                    Text(
                                      _enableOverlayMode
                                          ? 'States appear as overlays above the form'
                                          : 'States replace the entire content area',
                                      style: TextStyle(
                                        fontSize: 12,
                                        color: Colors.grey[600],
                                      ),
                                    ),
                                  ],
                                ),
                              ),
                              Switch(
                                value: _enableOverlayMode,
                                onChanged: (value) {
                                  _enableOverlayMode = value;
                                  if (value) {
                                    _currentState = SmartState.initial;
                                    _data.clear();
                                    _error = null;
                                    _nameController.clear();
                                    _emailController.clear();
                                  }
                                  onStateChanged();
                                  setState(() {});
                                },
                              ),
                            ],
                          ),
                          const SizedBox(height: 12),

                          // 🎯 NEW FEATURE: Error Display Toggle
                          Container(
                            padding: const EdgeInsets.all(12),
                            decoration: BoxDecoration(
                              color: Colors.blue.withValues(alpha: 0.05),
                              borderRadius: BorderRadius.circular(8),
                              border: Border.all(
                                color: Colors.blue.withValues(alpha: 0.2),
                              ),
                            ),
                            child: Row(
                              children: [
                                Icon(
                                  _enableSnackbar
                                      ? Icons.info_outline
                                      : Icons.error_outline,
                                  color: _enableSnackbar
                                      ? Colors.blue
                                      : Colors.red,
                                  size: 20,
                                ),
                                const SizedBox(width: 8),
                                Expanded(
                                  child: Column(
                                    crossAxisAlignment:
                                        CrossAxisAlignment.start,
                                    children: [
                                      const Text(
                                        'Error Display Style',
                                        style: TextStyle(
                                          fontWeight: FontWeight.w600,
                                          fontSize: 13,
                                        ),
                                      ),
                                      Text(
                                        _enableSnackbar
                                            ? 'Showing as Snackbar'
                                            : 'Showing as Full Screen',
                                        style: TextStyle(
                                          fontSize: 11,
                                          color: Colors.grey[600],
                                        ),
                                      ),
                                    ],
                                  ),
                                ),
                                Switch(
                                  value: _enableSnackbar,
                                  onChanged: (value) {
                                    _enableSnackbar = value;
                                    onStateChanged();
                                    setState(() {});
                                  },
                                ),
                              ],
                            ),
                          ),

                          if (_enableSnackbar) ...[
                            const SizedBox(height: 12),
                            // 📍 NEW FEATURE: Snackbar Position Toggle
                            Container(
                              padding: const EdgeInsets.all(12),
                              decoration: BoxDecoration(
                                color: Colors.green.withValues(alpha: 0.05),
                                borderRadius: BorderRadius.circular(8),
                                border: Border.all(
                                  color: Colors.green.withValues(alpha: 0.2),
                                ),
                              ),
                              child: Row(
                                children: [
                                  Icon(
                                    _snackbarAtTop
                                        ? Icons.vertical_align_top
                                        : Icons.vertical_align_bottom,
                                    color: Colors.green,
                                    size: 20,
                                  ),
                                  const SizedBox(width: 8),
                                  Expanded(
                                    child: Column(
                                      crossAxisAlignment:
                                          CrossAxisAlignment.start,
                                      children: [
                                        const Text(
                                          'Snackbar Position',
                                          style: TextStyle(
                                            fontWeight: FontWeight.w600,
                                            fontSize: 13,
                                          ),
                                        ),
                                        Text(
                                          _snackbarAtTop
                                              ? 'Showing at Top'
                                              : 'Showing at Bottom',
                                          style: TextStyle(
                                            fontSize: 11,
                                            color: Colors.grey[600],
                                          ),
                                        ),
                                      ],
                                    ),
                                  ),
                                  Switch(
                                    value: _snackbarAtTop,
                                    onChanged: (value) {
                                      _snackbarAtTop = value;
                                      onStateChanged();
                                      setState(() {});
                                    },
                                  ),
                                ],
                              ),
                            ),
                          ],

                          if (_enableOverlayMode) ...[
                            const SizedBox(height: 12),
                            // 🚪 NEW FEATURE: Dismissible Overlay Toggle
                            Container(
                              padding: const EdgeInsets.all(12),
                              decoration: BoxDecoration(
                                color: Colors.orange.withValues(alpha: 0.05),
                                borderRadius: BorderRadius.circular(8),
                                border: Border.all(
                                  color: Colors.orange.withValues(alpha: 0.2),
                                ),
                              ),
                              child: Row(
                                children: [
                                  Icon(
                                    _dismissibleOverlay
                                        ? Icons.touch_app
                                        : Icons.block,
                                    color: Colors.orange,
                                    size: 20,
                                  ),
                                  const SizedBox(width: 8),
                                  Expanded(
                                    child: Column(
                                      crossAxisAlignment:
                                          CrossAxisAlignment.start,
                                      children: [
                                        const Text(
                                          'Dismissible Overlay',
                                          style: TextStyle(
                                            fontWeight: FontWeight.w600,
                                            fontSize: 13,
                                          ),
                                        ),
                                        Text(
                                          _dismissibleOverlay
                                              ? 'Tap to dismiss'
                                              : 'Cannot dismiss',
                                          style: TextStyle(
                                            fontSize: 11,
                                            color: Colors.grey[600],
                                          ),
                                        ),
                                      ],
                                    ),
                                  ),
                                  Switch(
                                    value: _dismissibleOverlay,
                                    onChanged: (value) {
                                      _dismissibleOverlay = value;
                                      onStateChanged();
                                      setState(() {});
                                    },
                                  ),
                                ],
                              ),
                            ),
                          ],

                          const SizedBox(height: 12),
                          // Animation controls
                          if (!_enableOverlayMode) ...[
                            Row(
                              children: [
                                const Icon(Icons.animation,
                                    color: Colors.purple),
                                const SizedBox(width: 8),
                                Expanded(
                                  child:
                                      DropdownButton<SmartStateTransitionType>(
                                    value: _transitionType,
                                    isExpanded: true,
                                    onChanged:
                                        (SmartStateTransitionType? newValue) {
                                      if (newValue != null) {
                                        _transitionType = newValue;
                                        onStateChanged();
                                        setState(() {});
                                      }
                                    },
                                    items: SmartStateTransitionType.values.map<
                                        DropdownMenuItem<
                                            SmartStateTransitionType>>((
                                      SmartStateTransitionType value,
                                    ) {
                                      return DropdownMenuItem<
                                          SmartStateTransitionType>(
                                        value: value,
                                        child: Text(
                                          '${value.name.toUpperCase()} Animation',
                                          style: const TextStyle(fontSize: 14),
                                        ),
                                      );
                                    }).toList(),
                                  ),
                                ),
                              ],
                            ),
                            const SizedBox(height: 12),
                          ],
                          // Overlay Animation controls
                          if (_enableOverlayMode) ...[
                            Row(
                              children: [
                                const Icon(Icons.layers, color: Colors.purple),
                                const SizedBox(width: 8),
                                Expanded(
                                  child:
                                      DropdownButton<SmartStateTransitionType>(
                                    value: _overlayTransitionType,
                                    isExpanded: true,
                                    onChanged:
                                        (SmartStateTransitionType? newValue) {
                                      if (newValue != null) {
                                        _overlayTransitionType = newValue;
                                        onStateChanged();
                                        setState(() {});
                                      }
                                    },
                                    items: SmartStateTransitionType.values.map<
                                        DropdownMenuItem<
                                            SmartStateTransitionType>>((
                                      SmartStateTransitionType value,
                                    ) {
                                      return DropdownMenuItem<
                                          SmartStateTransitionType>(
                                        value: value,
                                        child: Text(
                                          'OVERLAY ${value.name.toUpperCase()}',
                                          style: const TextStyle(fontSize: 12),
                                        ),
                                      );
                                    }).toList(),
                                  ),
                                ),
                              ],
                            ),
                          ],
                        ],
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: const Icon(Icons.settings),
          onPressed: () => _showControlPanel(context, () => setState(() {})),
        ),
        title: const Text('SmartStateHandler Demo'),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: () => _simulateDataLoading(isRefresh: true),
          ),
        ],
      ),
      body: Column(
        children: [
          // Control buttons for testing different states
          _buildControlPanel(),

          // Main content with SmartStateHandler
          Expanded(
            child: SmartStateHandler<List<String>>(
              currentState: _currentState,
              successData: _data.isNotEmpty ? _data : null,
              errorObject: _error,
              hasMoreDataToLoad: _hasMoreData,
              enableDebugLogs: true,
              enableOverlayStates: _enableOverlayMode,
              showErrorAsSnackbar: _enableSnackbar,
              enableAnimations: true,
              overlayBackgroundColor: Colors.black.withValues(alpha: 0.3),
              overlayAlignment: Alignment.center,
              autoScrollThreshold: 100.0,
              loadMoreDebounceMs: 300,

              // 🎨 NEW: Overlay Configuration with dismissible options
              overlayConfig: SmartStateOverlayConfig(
                isDismissible: _dismissibleOverlay,
                barrierDismissible: _dismissibleOverlay,
                barrierColor: _enableOverlayMode
                    ? Colors.black.withValues(alpha: 0.6)
                    : Colors.black.withValues(alpha: 0.3),

                // Customize loading overlay
                loadingConfig: OverlayStateConfig(
                  backgroundColor: Colors.white.withValues(alpha: 0.95),
                  borderRadius: BorderRadius.circular(20),
                  padding: const EdgeInsets.all(32),
                  elevation: 12.0,
                  iconColor: Colors.blue,
                  textColor: Colors.black87,
                  iconSize: 48.0,
                  maxWidth: 300.0,
                ),

                // Customize error overlay
                errorConfig: OverlayStateConfig(
                  backgroundColor: Colors.white,
                  borderRadius: BorderRadius.circular(24),
                  padding: const EdgeInsets.all(32),
                  elevation: 16.0,
                  iconColor: Colors.red.shade700,
                  textColor: Colors.black87,
                  iconSize: 64.0,
                  maxWidth: 320.0,
                ),

                // Customize success overlay
                successConfig: OverlayStateConfig(
                  backgroundColor: Colors.white,
                  borderRadius: BorderRadius.circular(20),
                  padding: const EdgeInsets.all(32),
                  elevation: 12.0,
                  iconColor: Colors.green.shade600,
                  textColor: Colors.black87,
                  iconSize: 64.0,
                  maxWidth: 300.0,
                ),
              ),

              // 🎯 NEW: Snackbar Configuration with top/bottom positioning
              snackbarConfig: SmartStateSnackbarConfig(
                position: _snackbarAtTop
                    ? SnackbarPosition.top
                    : SnackbarPosition.bottom,
                behavior: SnackBarBehavior.floating,
                duration: const Duration(seconds: 4),
                showCloseIcon: true,
                margin: const EdgeInsets.all(16),
                errorConfig: SnackbarStateConfig(
                  backgroundColor: Colors.red.shade700,
                  textColor: Colors.white,
                  icon: Icons.error_outline_rounded,
                  iconSize: 24.0,
                  fontSize: 15.0,
                  fontWeight: FontWeight.w500,
                  borderRadius: BorderRadius.circular(12),
                  elevation: 8.0,
                  action: SnackBarAction(
                    label: 'RETRY',
                    textColor: Colors.white,
                    onPressed: () => _simulateDataLoading(isRefresh: true),
                  ),
                ),
                successConfig: SnackbarStateConfig(
                  backgroundColor: Colors.green.shade700,
                  textColor: Colors.white,
                  icon: Icons.check_circle_outline_rounded,
                  iconSize: 24.0,
                  fontSize: 15.0,
                  borderRadius: BorderRadius.circular(12),
                  elevation: 8.0,
                ),
              ),

              // Callback when overlay is dismissed
              onOverlayDismiss: () {
                if (_dismissibleOverlay) {
                  setState(() => _currentState = SmartState.initial);
                }
              },

              // Animation configuration
              animationConfig: SmartStateAnimationConfig(
                duration: const Duration(milliseconds: 400),
                curve: Curves.easeInOutCubic,
                type: _transitionType,
                overlayDuration: const Duration(milliseconds: 350),
                overlayCurve: Curves.easeOutBack,
                overlayType: _overlayTransitionType,
              ),

              // Custom icons for better UX
              errorIcon: Icons.error_outline_rounded,
              emptyIcon: Icons.inbox_outlined,
              offlineIcon: Icons.wifi_off_rounded,
              loadingIcon: Icons.hourglass_top_rounded,

              // Custom loading message
              customLoadingMessage: _enableOverlayMode
                  ? 'Submitting your form...'
                  : 'Loading awesome data...',

              // Text configuration
              textConfig: const SmartStateTextConfig(
                retryButtonText: 'Try Again',
                noDataFoundText: 'No items found',
                loadingText: 'Please wait...',
                offlineConnectionText: 'Check your internet connection',
              ),

              // Callbacks
              onRetryPressed: _enableOverlayMode
                  ? _submitForm
                  : () => _simulateDataLoading(isRefresh: true),
              onPullToRefresh: () => _simulateDataLoading(isRefresh: true),
              onLoadMoreData: _loadMoreData,

              // Base content for overlay mode (persistent form)
              baseContentBuilder:
                  _enableOverlayMode ? (context) => _buildFormContent() : null,

              // Initial state builder for non-overlay mode
              initialStateBuilder: _enableOverlayMode
                  ? null
                  : (context) {
                      return Center(
                        child: Padding(
                          padding: const EdgeInsets.all(24.0),
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                              Icon(
                                Icons.touch_app,
                                size: 64,
                                color: Theme.of(context).primaryColor,
                              ),
                              const SizedBox(height: 16),
                              Text(
                                'Welcome to SmartStateHandler Demo!',
                                style: Theme.of(
                                  context,
                                ).textTheme.headlineSmall,
                                textAlign: TextAlign.center,
                              ),
                              const SizedBox(height: 8),
                              Text(
                                'Click "Load Data" to begin or try different states using the buttons above.',
                                style: Theme.of(context).textTheme.bodyMedium,
                                textAlign: TextAlign.center,
                              ),
                              const SizedBox(height: 24),
                              ElevatedButton.icon(
                                onPressed: () => _simulateDataLoading(),
                                icon: const Icon(Icons.download),
                                label: const Text('Load Data'),
                              ),
                            ],
                          ),
                        ),
                      );
                    },

              // Custom overlay builders for better UX
              overlayLoadingBuilder: (context) => Container(
                padding: const EdgeInsets.all(20),
                decoration: BoxDecoration(
                  color: Colors.blue.withValues(alpha: 0.9),
                  borderRadius: BorderRadius.circular(12),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withValues(alpha: 0.2),
                      blurRadius: 8,
                      offset: const Offset(0, 4),
                    ),
                  ],
                ),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const CircularProgressIndicator(color: Colors.white),
                    const SizedBox(height: 12),
                    Text(
                      _enableOverlayMode
                          ? 'Submitting form...'
                          : 'Loading data...',
                      style: const TextStyle(color: Colors.white, fontSize: 16),
                    ),
                  ],
                ),
              ),

              overlayErrorBuilder: (context, error) => Container(
                padding: const EdgeInsets.all(20),
                decoration: BoxDecoration(
                  color: Colors.red.withValues(alpha: 0.9),
                  borderRadius: BorderRadius.circular(12),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withValues(alpha: 0.2),
                      blurRadius: 8,
                      offset: const Offset(0, 4),
                    ),
                  ],
                ),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const Icon(
                      Icons.error_outline,
                      color: Colors.white,
                      size: 40,
                    ),
                    const SizedBox(height: 12),
                    Text(
                      error?.toString() ?? 'An error occurred',
                      style: const TextStyle(color: Colors.white, fontSize: 16),
                      textAlign: TextAlign.center,
                    ),
                    const SizedBox(height: 16),
                    ElevatedButton(
                      onPressed: _enableOverlayMode
                          ? _submitForm
                          : () => _simulateDataLoading(isRefresh: true),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.white,
                        foregroundColor: Colors.red,
                      ),
                      child: const Text('Try Again'),
                    ),
                  ],
                ),
              ),

              overlaySuccessBuilder: (context) => Container(
                padding: const EdgeInsets.all(20),
                decoration: BoxDecoration(
                  color: Colors.green.withValues(alpha: 0.9),
                  borderRadius: BorderRadius.circular(12),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withValues(alpha: 0.2),
                      blurRadius: 8,
                      offset: const Offset(0, 4),
                    ),
                  ],
                ),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const Icon(
                      Icons.check_circle_outline,
                      color: Colors.white,
                      size: 40,
                    ),
                    const SizedBox(height: 12),
                    Text(
                      _enableOverlayMode
                          ? 'Form submitted successfully!'
                          : 'Data loaded successfully!',
                      style: const TextStyle(color: Colors.white, fontSize: 16),
                      textAlign: TextAlign.center,
                    ),
                  ],
                ),
              ),

              // Main data builder for non-overlay mode
              successDataBuilder: (context, data) {
                // ⚡ NEW v1.0.2: Memoize list building for performance
                return memoize(
                  'data-list',
                  () => NotificationListener<ScrollNotification>(
                    onNotification: (scrollInfo) {
                      // Auto-load more when near bottom
                      if (scrollInfo.metrics.pixels >=
                          scrollInfo.metrics.maxScrollExtent - 200) {
                        if (_hasMoreData &&
                            _currentState != SmartState.loadingMore) {
                          _loadMoreData();
                        }
                      }
                      return false;
                    },
                    child: ListView.builder(
                      key: const PageStorageKey('data_list'),
                      controller: _scrollController,
                      padding: const EdgeInsets.all(8),
                      itemCount: data.length +
                          (_currentState.isLoadingMore ? 1 : 0) +
                          (!_hasMoreData && data.isNotEmpty ? 1 : 0),
                      itemBuilder: (context, index) {
                        // Show loading indicator
                        if (_currentState.isLoadingMore &&
                            index == data.length) {
                          return const Padding(
                            padding: EdgeInsets.all(16.0),
                            child: Center(child: CircularProgressIndicator()),
                          );
                        }

                        // Show no more data message
                        if (!_hasMoreData &&
                            data.isNotEmpty &&
                            index ==
                                data.length +
                                    (_currentState.isLoadingMore ? 1 : 0)) {
                          return const Padding(
                            padding: EdgeInsets.all(16.0),
                            child: Center(
                              child: Text(
                                'No more data to load',
                                style: TextStyle(color: Colors.grey),
                              ),
                            ),
                          );
                        }

                        // Show regular item - ensure index is within data bounds
                        if (index >= data.length) {
                          return const SizedBox.shrink();
                        }

                        // 🔄 NEW v1.0.2: Use SmartStateKeepAlive for list optimization
                        return SmartStateKeepAlive(
                          keepAlive: true,
                          child: Card(
                            margin: const EdgeInsets.symmetric(
                              horizontal: 4,
                              vertical: 2,
                            ),
                            child: ListTile(
                              leading:
                                  CircleAvatar(child: Text('${index + 1}')),
                              title: Text(data[index]),
                              subtitle: Text('Subtitle for ${data[index]}'),
                              trailing: const Icon(Icons.arrow_forward_ios),
                              onTap: () {
                                ScaffoldMessenger.of(context).showSnackBar(
                                  SnackBar(
                                    content: Text('Tapped ${data[index]}'),
                                  ),
                                );
                              },
                            ),
                          ),
                        );
                      },
                    ),
                  ),
                  [data.length, _currentState, _hasMoreData],
                );
              },

              // Custom builders for different states
              loadingStateBuilder: (context) => const Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    CircularProgressIndicator(),
                    SizedBox(height: 16),
                    Text('Loading amazing content...'),
                  ],
                ),
              ),

              errorStateBuilder: (context, error) => Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    const Icon(
                      Icons.error_outline,
                      size: 64,
                      color: Colors.red,
                    ),
                    const SizedBox(height: 16),
                    Text(
                      'Oops! Something went wrong',
                      style: Theme.of(context).textTheme.headlineSmall,
                    ),
                    const SizedBox(height: 8),
                    Text(
                      error.toString(),
                      textAlign: TextAlign.center,
                      style: const TextStyle(color: Colors.grey),
                    ),
                    const SizedBox(height: 24),
                    ElevatedButton.icon(
                      onPressed: () => _simulateDataLoading(isRefresh: true),
                      icon: const Icon(Icons.refresh),
                      label: const Text('Try Again'),
                    ),
                  ],
                ),
              ),

              emptyStateBuilder: (context) => Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    const Icon(
                      Icons.inbox_outlined,
                      size: 64,
                      color: Colors.grey,
                    ),
                    const SizedBox(height: 16),
                    Text(
                      'No items found',
                      style: Theme.of(context).textTheme.headlineSmall,
                    ),
                    const SizedBox(height: 8),
                    const Text(
                      'Try refreshing or check back later',
                      style: TextStyle(color: Colors.grey),
                    ),
                    const SizedBox(height: 24),
                    ElevatedButton(
                      onPressed: () => _simulateDataLoading(isRefresh: true),
                      child: const Text('Refresh'),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  /// Builds form content for overlay mode demonstration
  Widget _buildFormContent() {
    return Center(
      child: SingleChildScrollView(
        padding: const EdgeInsets.all(24.0),
        child: Form(
          key: _formKey,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Text(
                'Contact Form Demo',
                style: Theme.of(context).textTheme.headlineSmall,
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 8),
              Text(
                'This form demonstrates overlay states. Submit to see loading/error/success overlays.',
                style: Theme.of(context).textTheme.bodyMedium,
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 32),

              // Name field
              TextFormField(
                controller: _nameController,
                decoration: const InputDecoration(
                  labelText: 'Full Name',
                  border: OutlineInputBorder(),
                  prefixIcon: Icon(Icons.person),
                ),
                validator: (value) {
                  if (value == null || value.trim().isEmpty) {
                    return 'Please enter your name';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 16),

              // Email field
              TextFormField(
                controller: _emailController,
                decoration: const InputDecoration(
                  labelText: 'Email Address',
                  border: OutlineInputBorder(),
                  prefixIcon: Icon(Icons.email),
                ),
                keyboardType: TextInputType.emailAddress,
                validator: (value) {
                  if (value == null || value.trim().isEmpty) {
                    return 'Please enter your email';
                  }
                  if (!value.contains('@')) {
                    return 'Please enter a valid email';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 24),

              // Submit button
              ElevatedButton.icon(
                onPressed: _currentState.isLoading ? null : _submitForm,
                icon: const Icon(Icons.send),
                label: Text(
                  _currentState.isLoading ? 'Submitting...' : 'Submit Form',
                ),
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildControlPanel() {
    return Container(
      padding: const EdgeInsets.all(16),
      color: Colors.grey[100],
      child: Container(
        width: double.infinity,
        padding: const EdgeInsets.all(12),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(8),
          border: Border.all(color: Colors.grey[300]!),
        ),
        child: Column(
          children: [
            Row(
              children: [
                Icon(
                  Icons.science_outlined,
                  color: Theme.of(context).primaryColor,
                  size: 20,
                ),
                const SizedBox(width: 8),
                Text(
                  _enableOverlayMode
                      ? 'Test Form States:'
                      : 'Test List States:',
                  style: const TextStyle(
                    fontWeight: FontWeight.bold,
                    fontSize: 14,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 8),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                _buildStateButton(
                  'Initial',
                  SmartState.initial,
                  Colors.grey,
                ),
                _buildStateButton(
                  'Loading',
                  SmartState.loading,
                  Colors.blue,
                ),
                _buildStateButton(
                  'Success',
                  SmartState.success,
                  Colors.green,
                ),
                _buildStateButton('Error', SmartState.error, Colors.red),
                if (!_enableOverlayMode) ...[
                  _buildStateButton(
                    'Empty',
                    SmartState.empty,
                    Colors.orange,
                  ),
                  _buildStateButton(
                    'Offline',
                    SmartState.offline,
                    Colors.purple,
                  ),
                ],
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildStateButton(String label, SmartState state, Color color) {
    return ElevatedButton(
      onPressed: () {
        setState(() {
          _currentState = state;
          switch (state) {
            case SmartState.initial:
              _data.clear();
              _error = null;
              break;
            case SmartState.loading:
              if (label == 'Load Data') {
                _simulateDataLoading();
              } else {
                // Just set loading state without simulation for testing
                _error = null;
              }
              break;
            case SmartState.success:
              _simulateSuccess();
              break;
            case SmartState.error:
              _simulateError();
              break;
            case SmartState.empty:
              _simulateEmpty();
              break;
            case SmartState.offline:
              _simulateOffline();
              break;
            case SmartState.loadingMore:
              // Simulate loading more data
              _loadMoreData();
              break;
          }
        });
      },
      style: ElevatedButton.styleFrom(
        backgroundColor: color,
        foregroundColor: Colors.white,
        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
      ),
      child: Text(label, style: const TextStyle(fontSize: 10)),
    );
  }
}

/// Example with GetX Controller + v1.0.2 Performance Features
/// Uncomment if you're using GetX
/*
class ProductController extends GetxController with SmartStateMemoization {
  final _state = SmartState.loading.obs;
  final _products = <Product>[].obs;
  final _error = RxnString();
  final _hasMoreData = true.obs;
  
  // 💾 NEW v1.0.2: Use cache for efficient data management
  final _cache = SmartStateCache<String, List<Product>>(maxSize: 100);

  SmartState get state => _state.value;
  List<Product> get products => _products;
  String? get error => _error.value;
  bool get hasMoreData => _hasMoreData.value;

  Future<void> fetchProducts({bool refresh = false}) async {
    try {
      if (refresh) {
        _state.value = SmartState.loading;
        _products.clear();
      } else {
        // 💾 Check cache first
        final cached = _cache.get('products');
        if (cached != null && cached.isNotEmpty) {
          _products.value = cached;
          _state.value = SmartState.success;
          return;
        }
      }
      
      // Your API call here
      final newProducts = await ApiService.getProducts();
      
      if (newProducts.isEmpty) {
        _state.value = SmartState.empty;
      } else {
        _products.value = newProducts;
        _state.value = SmartState.success;
        
        // 💾 Cache the result
        _cache.put('products', newProducts);
      }
    } catch (e) {
      _error.value = e.toString();
      _state.value = SmartState.error;
    }
  }

  Future<void> loadMore() async {
    if (!_hasMoreData.value) return;
    
    try {
      _state.value = SmartState.loadingMore;
      final newProducts = await ApiService.getProducts(page: _products.length ~/ 10 + 1);
      
      if (newProducts.isEmpty) {
        _hasMoreData.value = false;
      } else {
        _products.addAll(newProducts);
        
        // 💾 Update cache
        _cache.put('products', _products);
      }
      
      _state.value = SmartState.success;
    } catch (e) {
      _state.value = SmartState.success; // Keep current data visible
      // Handle pagination error separately if needed
    }
  }
  
  @override
  void onClose() {
    // 💾 Optionally clear cache on dispose
    _cache.clear();
    super.onClose();
  }
}

// Usage in Widget with memoization
class ProductsPage extends StatelessWidget {
  final ProductController controller = Get.put(ProductController());
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Obx(() => SmartStateHandler<List<Product>>(
        currentState: controller.state,
        successData: controller.products.isNotEmpty ? controller.products : null,
        errorObject: controller.error,
        hasMoreDataToLoad: controller.hasMoreData,
        
        // ⚡ NEW v1.0.2: Performance configurations
        loadMoreDebounceMs: 300,  // Debounce pagination
        enableDebugLogs: true,    // See cache/debounce in action
        
        onRetryPressed: () => controller.fetchProducts(refresh: true),
        onPullToRefresh: () => controller.fetchProducts(refresh: true),
        onLoadMoreData: controller.loadMore,
        
        successDataBuilder: (context, products) {
          // ⚡ Use memoization for expensive widgets
          return controller.memoize(
            'product-grid',
            () => GridView.builder(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                childAspectRatio: 0.7,
              ),
              itemCount: products.length,
              itemBuilder: (context, index) {
                // 🔄 NEW v1.0.2: KeepAlive for grid items
                return SmartStateKeepAlive(
                  child: ProductCard(products[index]),
                );
              },
            ),
            [products.length], // Rebuild only when count changes
          );
        },
      )),
    );
  }
}
*/

/// Example with Provider + v1.0.2 Performance Features
/// Uncomment if you're using Provider
/*
class ProductProvider extends ChangeNotifier with SmartStateMemoization {
  SmartState _state = SmartState.loading;
  List<Product> _products = [];
  String? _error;
  bool _hasMoreData = true;
  
  // 💾 NEW v1.0.2: Add cache
  final _cache = SmartStateCache<String, List<Product>>(maxSize: 100);

  SmartState get state => _state;
  List<Product> get products => _products;
  String? get error => _error;
  bool get hasMoreData => _hasMoreData;

  Future<void> fetchProducts({bool refresh = false}) async {
    try {
      if (!refresh) {
        // 💾 Check cache first
        final cached = _cache.get('products');
        if (cached != null && cached.isNotEmpty) {
          _products = cached;
          _state = SmartState.success;
          notifyListeners();
          return;
        }
      }
      
      _state = SmartState.loading;
      notifyListeners();
      
      final newProducts = await ApiService.getProducts();
      
      if (newProducts.isEmpty) {
        _state = SmartState.empty;
      } else {
        _products = newProducts;
        _state = SmartState.success;
        
        // 💾 Cache the data
        _cache.put('products', newProducts);
      }
    } catch (e) {
      _error = e.toString();
      _state = SmartState.error;
    }
    notifyListeners();
  }
  
  @override
  void dispose() {
    // 💾 Clean up cache
    _cache.clear();
    clearMemoCache(); // Clear memoization cache
    super.dispose();
  }
}
*/

/// v1.0.2 Performance Tips:
/// 
/// 1. Use SmartStateCache for data caching:
///    - Reduces API calls
///    - Improves perceived performance
///    - Automatic LRU cleanup
/// 
/// 2. Use SmartStateMemoization to prevent rebuilds:
///    - Memoize expensive widget builders
///    - Only rebuild when dependencies change
///    - Clear cache when needed with clearMemoCache()
/// 
/// 3. Use SmartStateKeepAlive in lists:
///    - Preserves widget state when scrolling
///    - Better for forms and interactive items
///    - Reduces unnecessary rebuilds
/// 
/// 4. Use const constructors for configs:
///    static const _textConfig = SmartStateTextConfig(...);
///    Enables Flutter optimizations
/// 
/// 5. Enable debouncing for pagination:
///    loadMoreDebounceMs: 300 (prevents rapid-fire requests)
2
likes
160
points
102
downloads

Publisher

unverified uploader

Weekly Downloads

A comprehensive Flutter widget for handling all UI states (loading, error, success, empty, offline) with animations, overlay mode, pagination, and extensive customization options.

Repository (GitHub)
View/report issues

Topics

#state-management #ui #widget #flutter-package #animations

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on smart_state_handler