flutter_notification_queue 0.2.0
flutter_notification_queue: ^0.2.0 copied to clipboard
A contextless, overlay-based in-app notification queue for Flutter. Supports spatial positioning, gesture intents, priority triage, notification grouping, and a rich event stream — all without BuildContext.
FlutterNotificationQueue #
A powerful, feature-rich overlay-based notification system for Flutter applications. FlutterNotificationQueue provides a comprehensive solution for displaying in-app notifications with advanced queuing, interactive gestures, multi-language support, and extensive customization options.
Key Features #
Advanced Notification System #
- Multiple Notification Types: Success, Error, Warning, Info with predefined styling
- Custom Notifications: Full control over appearance, behavior, and content
- Permanent Notifications: Stay visible until manually dismissed
- Auto-dismiss with Timer: Visual progress indicator and configurable duration
- Expandable Content: Tap to expand long messages with auto-pause on expansion
Intelligent Queue Management #
- Smart Queuing: FIFO-based queue system with configurable stack limits
- Multiple Queue Positions: 8 different screen positions (top, center, bottom + left, center, right)
- Stack Indicators: Visual "+N more" badges for queued notifications
- Channel System: Organized notification channels with individual configurations
- Dynamic Relocation: Drag notifications between different queue positions
Rich Interactive Features #
- Drag-to-Dismiss: Swipe notifications away in any direction
- Long-press Actions: Relocate or dismiss with long-press gestures
- Tap Actions: Button actions or tap-anywhere functionality
- Hover Effects: Adaptive close button with progressive enhancement
- Gesture Feedback: Smooth opacity changes during interactions
Internationalization & Accessibility #
- RTL Language Support: Automatic text direction detection for Arabic, Persian, Hebrew, and more
- Multi-language Examples: Comprehensive support for 10+ languages
- Responsive Design: Adaptive layouts for phone, tablet, and desktop
- Safe Area Integration: Automatic handling of notches and status bars
- Screen Reader Support: Proper semantic labels and accessibility features
Extensive Customization #
- Queue Styles: Flat, Filled, and Outlined notification styles
- Color Theming: Custom colors for each notification type and channel
- Animation Control: Configurable entrance/exit animations and curves
- Layout Customization: Margins, spacing, elevation, and border radius
- Custom Builders: Override notification UI with custom widgets
Installation #
Add FlutterNotificationQueue to your pubspec.yaml:
dependencies:
flutter_notification_queue: ^latest_version
Then run:
flutter pub get
Quick Start #
1. Initialize and Integrate #
Initialize the system and integrate the NotificationOverlay into your MaterialApp using the
builder pattern. This enables contextless notification support throughout your app.
void main() {
// 1. Initialize configuration
FlutterNotificationQueue.configure(
channels: {
const NotificationChannel(
name: 'success',
position: QueuePosition.topCenter,
defaultColor: Colors.green,
),
// add another channel
// const NotificationChannel(
// name: 'error',
// position: QueuePosition.topCenter,
// defaultColor: Colors.red,
// ),
// ...
},
queues: {
const NotificationQueue(
position: QueuePosition.topCenter,
style: FilledQueueStyle(
borderRadius: BorderRadius.all(Radius.circular(12)),
opacity: 0.9,
elevation: 8,
),
),
const NotificationQueue(
position: QueuePosition.bottomCenter,
style: FlatQueueStyle(),
),
},
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// 2. Integrate the overlay builder
builder: FlutterNotificationQueue.builder,
home: const MyHomePage(),
);
}
}
2. Display Notifications #
Use the .show() extension on any NotificationWidget to trigger a notification.
// Simple success notification
const NotificationWidget(
message: 'Operation completed successfully!',
title: 'Success',
channelName: 'success',
).show();
// Error with retry action
NotificationWidget(
channelName: 'error',
message: 'Network connection failed. Please try again.',
title: 'Connection Error',
action: NotificationAction.button(
label: 'Retry',
onPressed: () => retryOperation(),
),
).show();
Advanced Configuration #
Animation Control #
FlutterNotificationQueue provides powerful built-in transitions and allows for full customization.
Standard Transitions
The system tries to be smart about defaults. For example, a SlideTransitionStrategy will automatically slide from the correct direction based on the queue's position.
// Auto-slide from TopCenter
NotificationQueue(position: QueuePosition.topCenter, transition: const SlideTransitionStrategy(),
)
// Custom curve and duration
NotificationQueue(position: QueuePosition.bottomRight, transition: const SlideTransitionStrategy(
curve: Curves.elasticOut,
reverseCurve: Curves.easeOutExpo,
),
)
Customizing Properties
You can override standard properties like the slide offset or initial scale.
// Slide from the side instead of bottom
NotificationQueue(position: QueuePosition.bottomCenter, transition: const SlideTransitionStrategy(
slideOffset: Offset(-1, 0), // Slide from left
),
)
// Pop-in with custom scale and alignment
NotificationQueue(position: QueuePosition.centerRight, transition: const ScaleTransitionStrategy(
initialScale: 0.5, // Start/end at 50% size
alignment: Alignment.centerLeft, // Expand from left
),
)
Custom Animations (Builder)
For complete control, use the BuilderTransitionStrategy to define any animation inline.
NotificationQueue(position: QueuePosition.topCenter, transition: BuilderTransitionStrategy(
(context, animation, position, child) {
return RotationTransition(
turns: animation,
child: FadeTransition(
opacity: animation,
child: child,
),
);
},
),
)
Custom Queue Styles #
// Filled style with rounded corners
const FilledQueueStyle(
borderRadius: BorderRadius.all(Radius.circular(12)),
opacity: 0.9,
elevation: 8,
)
// Flat style for minimal design
const FlatQueueStyle(
borderRadius: BorderRadius.zero,
opacity: 0.8,
elevation: 2,
)
// Outlined style with borders
const OutlinedQueueStyle(
borderRadius: BorderRadius.all(Radius.circular(8)),
opacity: 0.7,
elevation: 4,
)
Drag and Gesture Behaviors #
FNQ uses an Intent-First interaction model. Each queue independently declares what a drag or long-press means:
Dismiss: Swipe to dismiss. Configurable zones:DismissZone.sideEdgesorDismissZone.naturalDirection.Reorder: Drag-to-reorder within the current stack. Live-shifting layout with hysteresis-based slot targeting.Relocate: Drag to relocate a card to a different queue position (e.g. park to a corner).ReorderAndRelocate: Reorder within the stack by default; drag past a configurable escape threshold to relocate.Disabled: No drag interaction.
NotificationQueue(
position: QueuePosition.topRight,
dragBehavior: const Dismiss(),
longPressDragBehavior: Relocate.to({QueuePosition.bottomRight}),
)
NotificationQueue(
position: QueuePosition.topLeft,
dragBehavior: const Reorder(),
longPressDragBehavior: ReorderAndRelocate.to(
positions: {QueuePosition.bottomLeft},
),
)
Tip
Relocation Intelligence: When you define Relocate.to({...}) or ReorderAndRelocate.to(positions: {...}) for a queue, the system automatically:
- Registers sibling queues for all target positions (no need to define them manually).
- Clones all characteristics (style, transition, spacing, maxStackSize) from the source queue to siblings.
- Adds the source position to the target set so notifications can be dragged back home.
Interaction Details #
- Reorder with Hysteresis: When using
ReorderorReorderAndRelocate, the insertion slot targeting uses a gravity-well algorithm — the active target zone holds a larger magnetic hit area, preventing accidental slot switches from minor pointer wobble during drags. - Selection Reticle: The current insertion target is highlighted with a glowing border and a subtle background dimming so the drop slot is always visually clear.
- Self-Drop Suppression: Dragging a card back to its original position suppresses all insertion feedback and shows an empty placeholder, making it easy to cancel a reorder.
- Hover-to-Pause: On desktop, hovering over a notification with an active auto-dismiss timer pauses the countdown. The timer resumes when the pointer leaves.
- Spring Snapback: Releasing a drag that did not cross the activation threshold returns the card to its starting position using configurable spring physics (
SpringPhysicsConfiguration).
Close Button Behaviors #
const AlwaysVisible() // Always visible
const VisibleOnHover() // Adaptive visibility (shows subtle opacity for touch, fully hidden on desktop until hover)
const Hidden() // Never show close button (gesture/tap-only dismissal)
Architecture Concepts: Channels vs. Queues #
FNQ decouples the what (styling and intent defaults) from the where (spatial layout and gestures):
NotificationQueue: A spatial layout container fixed to a specificQueuePosition(e.g.topLeft,bottomCenter). It governs behavior and constraints: entrance/exit animations, max stack sizes, drag-to-relocate destinations, drag-to-dismiss behavior, and stack overflow strategies.NotificationChannel: A logical category for messages (e.g.success,chat_burst). It governs visual defaults: colors, icons, default priority, and default dismiss durations. Each channel routes to a specificQueuePosition.
Note
When a notification is shown, it maps to a channel. The channel's position decides which queue it goes to. If the queue configuration specifies an override for styling/gestures, the queue's configuration takes precedence.
Notification Grouping (Bundling) #
To prevent visual clutter when multiple notifications from the same source arrive rapidly, FNQ supports automatic stacking and group collapse/expansion:
// Configure queue with grouping behavior
NotificationQueue(
position: QueuePosition.topCenter,
groupingBehavior: QueueGroupingBehavior(
enabled: true,
maxBeforeGrouping: 3, // Collapse after 3 notifications arrive
maxStackedLayers: 2, // Number of background card decks to show
stackStepOffset: 6.0, // Vertical spacing between card decks
stackScaleMultiplier: 0.05, // Scale reduction per stacked deck
enableGroupSwipeDismiss: true, // Dismiss the whole group on swipe
groupDismissThreshold: 0.4, // Swipe displacement ratio to dismiss group
),
)
Notifications sharing a groupKey (which defaults to the channelName) will automatically bundle. The user can tap the bundle indicator to expand or collapse it.
Priority Triage #
FNQ evaluates notifications using semantic priorities:
NotificationPriority.lowNotificationPriority.normalNotificationPriority.highNotificationPriority.critical
If a queue is full (exceeds maxStackSize) and a higher-priority notification arrives, the priority triage engine automatically evicts the lowest-priority active notification (triggering a DismissReason.evicted event) and places the incoming card immediately. Evicted notifications are pushed back to the pending queue to be re-displayed when higher-priority ones clear.
Backpressure & Overflow Strategy #
Configure how a queue handles backpressure when the pending queue limit is reached:
NotificationQueue(position: QueuePosition.topCenter, maxStackSize: 3,
maxPendingSize: 10,
overflowStrategy: QueueOverflowStrategy.discardOldest, // or discardNewest
)
QueueOverflowStrategy.discardOldest: Drops the oldest notification of the lowest priority in the pending queue.QueueOverflowStrategy.discardNewest: Rejects the incoming notification immediately if the pending list is full.
Observability (FnqEvent Stream) #
Observe the lifecycle of all notifications in real-time by subscribing to the global event stream. This stream is stable across reconfiguration calls:
FlutterNotificationQueue.events.listen((event) {
switch (event) {
case NotificationQueued(:final notification):
analytics.track('notif_displayed', id: notification.id);
case NotificationDismissed(:final notification, :final reason):
if (reason == DismissReason.timeout) {
analytics.track('notif_timeout', id: notification.id);
}
case NotificationTapped(:final notification, :final behavior):
analytics.track('notif_tapped', id: notification.id);
case NotificationRelocated(:final notification, :final from, :final to):
analytics.track('notif_relocated', from: from.name, to: to.name);
case NotificationReordered(:final notification, :final toIndex):
case QueueOverflowed(:final queue, :final dropped):
// Group-specific events:
case NotificationGroupExpanded():
case NotificationGroupCollapsed():
case NotificationGroupDismissed():
default:
break;
}
});
Event History Log
You can configure an in-memory, bounded LIFO ring buffer to automatically log past events. Query the cache or clear it on demand:
// 1. Opt-in by specifying maxHistoryEntries
FlutterNotificationQueue.configure(
maxHistoryEntries: 50,
);
// 2. Query history with optional filters (channelName, dismissReason, since, limit)
List<FnqEvent> logs = FlutterNotificationQueue.getHistory(
channelName: 'error',
limit: 10,
);
// 3. Clear history logs programmatically
FlutterNotificationQueue.clearHistory();
Tip
Performance & Disabling: Set maxHistoryEntries to 0 (the default) or less to completely disable history logging. This cancels all internal stream subscriptions and releases the in-memory cache, ensuring zero runtime CPU or memory overhead.
Dynamic Channel Parking
Dynamic Channel Parking allows notification channels to update their target queues dynamically based on drag-and-drop gestures:
FlutterNotificationQueue.configure(
enableDynamicChannelParking: true,
);
When enabled:
- If a user drags a notification belonging to a channel (e.g.
info) from its default position and relocates it into another queue position, the system dynamically updates the routing rule for that channel. - All future notifications dispatched on the
infochannel will automatically be delivered to the new queue position.
🌍 Multi-language Support #
FlutterNotificationQueue automatically detects text direction and supports RTL languages:
// Arabic notification
NotificationWidget(
title: 'إشعار هام',
message: 'تم تحديث التطبيق بنجاح. يرجى إعادة تشغيل التطبيق.',
action: NotificationAction.button(
label: 'إعادة التشغيل',
onPressed: () => restartApp(),
),
).show();
// Persian notification
NotificationWidget(
title: 'اطلاعیه',
message: 'عملیات با موفقیت انجام شد! سیستم آماده استفاده است.',
action: NotificationAction.button(
label: 'تأیید',
onPressed: () => confirmAction(),
),
).show();
Platform-Specific Features #
Mobile (iOS/Android) #
- Native gesture recognition
- Haptic feedback support
- Safe area integration
- Optimized touch targets
Web #
- Hover effects and interactions
- Keyboard navigation support
- Close button always available
- Responsive breakpoints
Desktop (Windows/macOS/Linux) #
- Mouse drag support
- Keyboard shortcuts
- Window-aware positioning
- High DPI support
Use Cases #
Success Messages #
NotificationWidget(
message: 'File saved successfully!',
title: 'Success',
channelName: 'success',
dismissDuration: Duration(seconds: 3),
).show();
Error Handling #
NotificationWidget(
message: 'Failed to connect to server. Please check your internet connection.',
title: 'Connection Error',
channelName: 'error',
action: NotificationAction.button(
label: 'Retry',
onPressed: () => retryConnection(),
),
).show();
Warning Notifications #
NotificationWidget(
message: 'Low storage space detected. Tap to manage.',
title: 'Storage Warning',
channelName: 'warning',
action: NotificationAction.onTap(
onPressed: () => openStorageSettings(),
),
).show();
Info Messages #
NotificationWidget(
message: 'New features available! Check out our latest update.',
title: 'App Update',
channelName: 'info',
action: NotificationAction.button(
label: 'Learn More',
onPressed: () => showUpdateDetails(),
),
).show();
Permanent & Pinned Notifications #
By default, the notification's auto-dismiss timer is determined by the channel default. You can override it to be permanent (staying on screen indefinitely) in two ways:
- Setting
permanent: true: Keep a notification on screen even if the channel has an auto-dismiss duration. - Setting
dismissDuration: null: Backward-compatible way to mark a notification as permanent.
// Keep a notification on screen indefinitely
NotificationWidget(
message: 'Ongoing file sync in progress...',
permanent: true,
).show();
API Reference #
Core Components #
FlutterNotificationQueue: The primary entry point.configure(): Configures global queues and channels.builder: Integration hook forMaterialApp.builder.events: The stable global broadcast stream of lifecycle events.
NotificationWidget: The main configuration for individual notifications.permanent: Keep notification on screen regardless of channel defaults.
NotificationChannel: Defines shared behavior and styling for groups of notifications.standardChannels(): Returns a set of standard channels (success, error, info, warning).successChannel(),errorChannel(), etc.: Factory methods for common channel types.
NotificationQueue: Manages the lifecycle and rendering constraints of a specific screen position.NotificationQueue(): Default constructor for creating a standard queue configuration.
NotificationAction: Definable user interactions (buttons, taps, gestures).
Queue Positions #
QueuePosition.topLeftQueuePosition.topCenterQueuePosition.topRightQueuePosition.centerLeftQueuePosition.centerRightQueuePosition.bottomLeftQueuePosition.bottomCenterQueuePosition.bottomRight
Action Types #
// Button action
NotificationAction.button(
label: 'Action Label',
onPressed: () => handleAction(),
);
// Tap action
NotificationAction.onTap(
onPressed: () => handleTap(),
);
Customization Examples #
Custom Notification Builder #
NotificationWidget(
message: 'Custom styled notification',
builder: (context, notification) => Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.purple, Colors.blue],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
),
child: Material(
color: Colors.transparent,
child: Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
Icon(Icons.star, color: Colors.white),
SizedBox(width: 12),
Expanded(
child: Text(
notification.message,
style: TextStyle(color: Colors.white),
),
),
],
),
),
),
).show();
Custom Queue Indicator #
NotificationQueue(position: QueuePosition.topCenter, queueIndicatorBuilder: (context, count, config) => Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(12),
),
child: Text(
'+$count',
style: const TextStyle(color: Colors.white, fontSize: 12),
),
),
)
Performance Features #
- Efficient Rendering: Single overlay for all notifications
- Memory Management: Automatic cleanup and disposal
- Lazy Loading: Notifications built only when needed
- Gesture Optimization: Smooth 60fps interactions
- Queue Efficiency: O(1) queue operations
📈 Migration Guide #
From 0.1.x to 0.2.0 #
Version 0.2.0 consolidates the API and removes position-specific queue subclasses to simplify integration.
Key Changes:
-
Subclass Removal / Unified
NotificationQueue: All position-specific subclasses ofNotificationQueue(e.g.TopLeftQueue,TopCenterQueue,BottomCenterQueue) have been removed. Use the single concreteNotificationQueueclass directly and specify itspositionparameter.// Old (deprecated in 0.1.0, removed in 0.2.0) -const TopLeftQueue( - style: FlatQueueStyle(), -) // New (v0.2.0+) +const NotificationQueue( + position: QueuePosition.topLeft, + style: FlatQueueStyle(), +) -
Simplified
QueuePositionHelpers:QueuePosition.generateQueue(...)andQueuePosition.generateQueueFrom(...)now directly construct and return a concreteNotificationQueueinstance rather than a subclass. -
Internal State Decoupling: Configuration blueprints in
NotificationWidgetare now separated from active runtime state (dismiss timers, pinned states, priority) using the internalNotificationEntryclass, preventing anyGlobalKeyconflicts.
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
📄 License #
This project is licensed under the BSD 3-Clause License - see the LICENSE file for details.