🍞 Flutter Easy Messages

A powerful, elegant, and highly customizable Flutter package for displaying toast notifications and snackbars. Built for both simple notifications and complex enterprise scenarios like API error handling, file operations, and request tracking.

Pub License: MIT Tests Analysis Dart Flutter


✨ Features Overview

🎯 Core Features

  • 🍞 Toast Notifications - Overlay-based notifications with smooth animations and multiple styling options
  • πŸ“± Responsive Snackbars - Automatic sizing that adapts to mobile, tablet, and desktop screens
  • 🎨 Message Types - 4 pre-styled message types: Error, Success, Info, Warning
  • πŸ“ 9 Position Options - Complete positioning flexibility: top/center/bottom with left/center/right alignment
  • ⚑ Smooth Animations - Customizable entry, exit, and pulse animations with preset speeds
  • πŸ”„ Queue & Replace Modes - Control how multiple messages are handled (queue sequentially or replace)
  • πŸ“² Full Responsiveness - Automatic layout adjustments for all screen sizes and orientations
  • β™Ώ Accessibility - Screen reader integration with semantic labels for inclusive apps
  • 🎯 Smart Config - Global defaults with per-message overrides for maximum flexibility

πŸš€ Advanced Features (Enterprise-Ready)

  • πŸ”˜ Action Buttons - Add interactive retry, cancel, or custom buttons to toasts
  • πŸ“‹ Expandable Error Details - Display detailed error information that users can expand on demand
  • ⏳ Persistent Toasts - Long-running operation toasts that don't auto-dismiss
  • πŸ‘† Dismissible Toasts - User-controlled dismissal with intuitive tap-to-close gesture
  • πŸ†” Request ID Tracking - Track and manage toasts by request ID for API correlation
  • πŸ“ž Lifecycle Callbacks - React to toast lifecycle events (onShown, onDismissed)
  • πŸ“ Custom Text Styling - Full control over font size, weight, family, and alignment
  • 🌐 Context-Free Toasts - Show toasts anywhere in your app without BuildContext
  • 🎯 Deduplication - Built-in prevention of duplicate messages

🎬 Demo

Message Types & Colors
Message Types
Snackbars
Snackbars
Toast Positions (9 Options)
Toast Positions
Custom Styling & Icons
Custom Styling
Animations & Durations
Animations & Durations
---

πŸš€ Quick Start (2 Steps!)

Step 1: Add to Dependencies

# pubspec.yaml
dependencies:
  flutter_easy_messages: ^0.1.0

Then run:

flutter pub get

Step 2: Use It!

import 'package:flutter_easy_messages/flutter_easy_messages.dart';

// Inside a widget with BuildContext
showAppToast(
  'Success!',
  context: context,
  messageType: MessageType.success,
);

That's it! πŸŽ‰ No complex setup or configuration needed to get started.


πŸ“– Complete Usage Guide

🍞 Basic Toast Notifications

Using Message Types

// Success Toast - Green with checkmark
showAppToast(
  'Operation completed successfully!',
  context: context,
  messageType: MessageType.success,
);

// Error Toast - Red with error icon
showAppToast(
  'Something went wrong',
  context: context,
  messageType: MessageType.error,
);

// Info Toast - Blue with info icon
showAppToast(
  'Here is some useful information',
  context: context,
  messageType: MessageType.info,
);

// Warning Toast - Orange with warning icon
showAppToast(
  'Please be careful with this action',
  context: context,
  messageType: MessageType.warning,
);

Custom Styling

showAppToast(
  'Custom styled notification',
  context: context,
  backgroundColor: Colors.purple,
  duration: Duration(seconds: 3),
  position: MessagePosition.topCenter,
  borderRadius: 20,
  icon: Icon(Icons.favorite, color: Colors.white),
  fontSize: 16,
  fontWeight: FontWeight.bold,
  fontFamily: 'Roboto',
  maxLines: 2,
  offset: Offset(0, 10),
);

Position Your Toasts (9 Options)

// Top positions
MessagePosition.topLeft        // ↖️  Top-left corner
MessagePosition.topCenter      // ⬆️  Top center
MessagePosition.topRight       // ↗️  Top-right corner

// Center positions (vertically centered)
MessagePosition.centerLeft     // ⬅️  Center-left
MessagePosition.center         // β­• Center screen
MessagePosition.centerRight    // ➑️  Center-right

// Bottom positions
MessagePosition.bottomLeft     // ↙️  Bottom-left corner
MessagePosition.bottomCenter   // ⬇️  Bottom center (default)
MessagePosition.bottomRight    // β†˜οΈ  Bottom-right corner

Real example:

showAppToast(
  'Important notification',
  context: context,
  messageType: MessageType.info,
  position: MessagePosition.topRight,
  duration: Duration(seconds: 4),
);

Managing Multiple Messages

// Replace Mode (default) - Only one toast visible at a time
for (int i = 1; i <= 3; i++) {
  showAppToast(
    'Message $i',
    context: context,
    behavior: MessageBehavior.replace,
  );
}
// Result: Only 'Message 3' is shown

// Queue Mode - Messages shown sequentially
for (int i = 1; i <= 3; i++) {
  showAppToast(
    'Message $i',
    context: context,
    behavior: MessageBehavior.queue,
  );
}
// Result: All 3 messages shown in order

πŸ“‹ Snackbars

// Simple snackbar notification
showAppSnackBar(
  'This is a snackbar message',
  context: context,
  messageType: MessageType.success,
);

// Snackbar with custom styling
showAppSnackBar(
  'Customized snackbar',
  context: context,
  messageType: MessageType.info,
  duration: Duration(seconds: 4),
  fontSize: 16,
  fontWeight: FontWeight.w600,
);

🌐 Context-Free Toasts (No BuildContext Needed!)

Perfect for showing toasts from services, utilities, and API calls without needing a BuildContext.

Setup (One-Time in main.dart)

import 'package:flutter_easy_messages/flutter_easy_messages.dart';

void main() {
  // Create a navigator key
  final navigatorKey = GlobalKey<NavigatorState>();

  // Set the navigator key FIRST
  EasyMessageConfig.setNavigatorKey(navigatorKey);

  // Optional: Configure global defaults
  EasyMessageConfig.configure(
    toastDuration: Duration(seconds: 2),
    borderRadius: 12,
    toastPosition: MessagePosition.bottomCenter,
    enablePulse: true,
  );

  runApp(MyApp(navigatorKey: navigatorKey));
}

class MyApp extends StatelessWidget {
  final GlobalKey<NavigatorState> navigatorKey;

  const MyApp({required this.navigatorKey, super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: navigatorKey,  // ← Pass the same key
      home: HomeScreen(),
    );
  }
}

Usage (Show Toasts Anywhere!)

// No context required!
showAppToast('Welcome back!', messageType: MessageType.success);

// In services
class AuthService {
  void logout() {
    showAppToast('Logged out successfully', messageType: MessageType.info);
  }
}

// In utility functions
void handleError(String error) {
  showAppToast(error, messageType: MessageType.error);
}

// In API calls
Future<void> fetchData() async {
  try {
    final data = await api.getData();
  } catch (e) {
    showAppToast('Failed to load data', messageType: MessageType.error);
  }
}

🎯 Advanced Features (Enterprise-Grade)

πŸ”˜ Action Buttons - Add Interactivity

Add retry, cancel, or custom action buttons to your toasts.

showAppToast(
  'Failed to upload document.pdf',
  context: context,
  messageType: MessageType.error,
  duration: Duration(seconds: 10),
  actions: [
    ToastAction(
      label: 'Retry',
      color: Colors.green,
      textColor: Colors.white,
      onPressed: () {
        retryUpload();
      },
    ),
    ToastAction(
      label: 'Cancel',
      color: Colors.red,
      textColor: Colors.white,
      onPressed: () {
        cancelOperation();
      },
    ),
  ],
);

πŸ“‹ Error Details - Expandable Information

Display detailed error information that users can expand when needed.

showAppToast(
  '❌ API Request Failed',
  context: context,
  messageType: MessageType.error,
  duration: Duration(seconds: 8),
  errorDetails:
      'Status Code: 500\n'
      'Endpoint: /api/v1/upload\n'
      'Error: Internal Server Error\n'
      'Request ID: REQ-123456789',
);

User sees: ❌ API Request Failed [Show details]
Tapping reveals full error information.

⏳ Persistent Toasts - For Long Operations

Perfect for upload, download, or processing notifications that shouldn't auto-dismiss.

showAppToast(
  '⏳ Processing PDF file...',
  context: context,
  messageType: MessageType.info,
  isPersistent: true,        // Won't auto-dismiss
  dismissible: true,          // User can tap to close
  duration: Duration(seconds: 999),
  requestId: 'processing_pdf_001',
  onShown: () {
    // Called when toast appears
    startProcessing();
  },
  onDismissed: () {
    // Called when dismissed
    cleanup();
  },
);

// Later: Programmatically clear the toast
ToastManager.clearByRequestId('processing_pdf_001');

πŸ‘† Dismissible Toasts

Enable users to close notifications with a tap.

showAppToast(
  'πŸ‘† Tap anywhere on this toast to close it',
  context: context,
  messageType: MessageType.warning,
  dismissible: true,
  duration: Duration(seconds: 5),
);

πŸ†” Request ID Tracking

Track and manage toasts by request IDβ€”ideal for API request correlation and preventing duplicates.

// Show toast for a specific request
showAppToast(
  'Processing order...',
  context: context,
  messageType: MessageType.info,
  isPersistent: true,
  requestId: 'order_checkout_001',
);

// Check how many toasts exist for this request
int count = ToastManager.getToastCountByRequestId('order_checkout_001');

// Clear all toasts for this request
await ToastManager.clearByRequestId('order_checkout_001');

// Clear everything
await ToastManager.clearAll();

πŸ“ž Lifecycle Callbacks

React to toast eventsβ€”perfect for analytics, logging, and state management.

showAppToast(
  'Processing...',
  context: context,
  messageType: MessageType.info,
  isPersistent: true,
  onShown: () {
    // Called when toast appears on screen
    analytics.logEvent('notification_shown', {'message': 'Processing'});
    startTimer();
  },
  onDismissed: () {
    // Called when toast is dismissed
    analytics.logEvent('notification_dismissed', {'message': 'Processing'});
    stopTimer();
    refreshUI();
  },
);

βš™οΈ Global Configuration

Configure default behavior once in your main() function:

void main() {
  EasyMessageConfig.configure(
    toastDuration: Duration(seconds: 2),
    snackBarDuration: Duration(seconds: 3),
    toastEntryAnimationDuration: Duration(milliseconds: 400),
    toastExitAnimationDuration: Duration(milliseconds: 300),
    borderRadius: 12,
    toastPosition: MessagePosition.bottomCenter,
    toastBehavior: MessageBehavior.replace,
    enablePulse: true,
  );
  
  runApp(MyApp());
}

Animation Presets

Use built-in animation presets for consistent animations:

// Fast animations (200ms)
EasyMessageConfig.configure(
  toastEntryAnimationDuration: AnimationPresets.fast.entry,
  toastExitAnimationDuration: AnimationPresets.fast.exit,
);

// Normal animations (400ms) - default
EasyMessageConfig.configure(
  toastEntryAnimationDuration: AnimationPresets.normal.entry,
  toastExitAnimationDuration: AnimationPresets.normal.exit,
);

// Slow animations (600ms)
EasyMessageConfig.configure(
  toastEntryAnimationDuration: AnimationPresets.slow.entry,
  toastExitAnimationDuration: AnimationPresets.slow.exit,
);

πŸ’‘ Real-World Examples

API Error Handling with Retry

Future<void> uploadFile(File file) async {
  try {
    showAppToast(
      '⏳ Uploading ${file.name}...',
      context: context,
      messageType: MessageType.info,
      isPersistent: true,
      requestId: 'upload_${file.hashCode}',
    );
    
    await api.uploadFile(file);
    await ToastManager.clearByRequestId('upload_${file.hashCode}');
    
    showAppToast(
      'βœ… Upload complete!',
      context: context,
      messageType: MessageType.success,
    );
  } catch (e) {
    showAppToast(
      '❌ Upload failed',
      context: context,
      messageType: MessageType.error,
      duration: Duration(seconds: 10),
      actions: [
        ToastAction(
          label: 'Retry',
          color: Colors.green,
          onPressed: () => uploadFile(file),
        ),
      ],
    );
  }
}

Form Validation

void validateForm(Map<String, String> data) {
  if (data['email']?.isEmpty ?? true) {
    showAppToast(
      'Email is required',
      context: context,
      messageType: MessageType.error,
    );
    return;
  }
  submitForm(data);
}

🧠 Best Practices

βœ… Do's

  • βœ“ Keep messages short (1-2 lines)
  • βœ“ Use appropriate message types
  • βœ“ Test on multiple screen sizes
  • βœ“ Use context-free toasts in services
  • βœ“ Use request IDs for long operations

❌ Don'ts

  • βœ— Spam users with multiple toasts
  • βœ— Keep toasts visible too long
  • βœ— Use complex layouts
  • βœ— Reconfigure globally too frequently

πŸ†˜ Troubleshooting

Issue Solution
Toasts not showing Set navigator key via EasyMessageConfig.setNavigatorKey()
Choppy animations Try AnimationPresets.fast or run in release mode
Message stacking Use MessageBehavior.replace (default)

πŸ“Š Quality & Testing

  • βœ… 30/30 tests passing (100%)
  • βœ… 0 code analysis issues
  • βœ… Full null safety
  • βœ… 90%+ documentation coverage
  • βœ… Flutter 1.17.0+
  • βœ… Dart 3.11.1+

πŸ“± Responsive Design

Automatic layout adjustment for all screen sizes:

πŸ“± Mobile      πŸ“± Tablet       πŸ–₯️ Desktop
(<600px)       (600-1024px)    (>1024px)

All toasts adapt to screen width, height, and safe areas.


πŸ“ Project Structure

flutter_easy_messages/
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ flutter_easy_messages.dart
β”‚   └── src/
β”‚       β”œβ”€β”€ toast_helper.dart
β”‚       β”œβ”€β”€ toast_manager.dart
β”‚       β”œβ”€β”€ message_config.dart
β”‚       β”œβ”€β”€ message_type.dart
β”‚       β”œβ”€β”€ message_position.dart
β”‚       β”œβ”€β”€ message_behavior.dart
β”‚       └── ... (other modules)
β”œβ”€β”€ test/
β”‚   └── flutter_easy_messages_test.dart
β”œβ”€β”€ example/
β”‚   └── ... (demo app)
└── pubspec.yaml

🀝 Contributing

Contributions are welcome! Please:

  1. Report issues on GitHub
  2. Submit pull requests with improvements
  3. Help improve documentation

πŸ“„ License

MIT License - see LICENSE file for details


πŸ“š Resources


Made with ❀️ for the Flutter community

⭐ Star on GitHub if you find this helpful!

Libraries

flutter_easy_messages
A Flutter package for easy toast and snackbar messages.