flutter_notification_queue 0.4.0 copy "flutter_notification_queue: ^0.4.0" to clipboard
flutter_notification_queue: ^0.4.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 #

CI Pub Version Pub Likes Pub Points License: BSD 3-Clause Platform: Flutter

A production-grade, overlay-based spatial notification engine for Flutter applications.

Try NFQ Studio Web Demo

FlutterNotificationQueue (FNQ) provides a comprehensive solution for displaying in-app notifications with spatial queue placement, Intent-First interactive drag/reorder/relocate gestures, notification bundling, priority triage preemption, dynamic channel parking, real-time observability, and deep theming.


💡 Core Architecture & Mental Model #

FNQ cleanly decouples what a message represents from where and how it is rendered on screen:

┌────────────────────────┐      ┌─────────────────────────┐
│    AppNotification     │      │   NotificationChannel   │
│  (Data Payload Intent) │────► │  (Visual Category/Theme)│
└────────────────────────┘      └────────────┬────────────┘
                                             │ Maps route position
                                             ▼
┌────────────────────────┐      ┌─────────────────────────┐
│ NotificationController │      │    NotificationQueue    │
│  & NotificationScope   │ ◄──► │ (Spatial Position/Rules)│
│  (Lifecycle & State)   │      │ (Gestures & Grouping)   │
└────────────────────────┘      └─────────────────────────┘
  1. AppNotification: Pure data payload describing notification content, title, message, priority, dismiss duration, actions, and tap behaviors.
  2. NotificationChannel: Logical grouping for messages (e.g., success, error, chat). Governs default colors, icons, priorities, auto-dismiss durations, and target queue positions.
  3. NotificationQueue: Spatial layout container fixed to a QueuePosition (e.g., topRight, bottomCenter). Controls physics, max stack limits, overflow strategies, drag behaviors (Dismiss, Reorder, Relocate, ReorderAndRelocate), and grouping decks.
  4. NotificationController & NotificationScope: State management handle and widget-tree dependency injector. Manages lifecycle, contextless or context-aware dispatching, event streams, history logging, and live runtime reconfiguration.

⚡ Quick Start #

1. Add Dependency #

Add flutter_notification_queue to your pubspec.yaml:

dependencies:
  flutter_notification_queue: ^0.4.0

2. Initialize and Mount Scope #

Initialize a NotificationController and wrap your MaterialApp with NotificationScope:

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

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    // 1. Instantiate the controller with queues and channels
    final controller = NotificationController(
      channels: {
        const NotificationChannel(
          name: 'info',
          position: QueuePosition.topRight,
          defaultColor: Color(0xFF0EA5E9),
        ),
        const NotificationChannel(
          name: 'error',
          position: QueuePosition.topRight,
          defaultColor: Color(0xFFEF4444),
        ),
      },
      queues: {
        const NotificationQueue(
          position: QueuePosition.topRight,
          maxStackSize: 3,
          style: FilledQueueStyle(
            borderRadius: BorderRadius.all(Radius.circular(12)),
            elevation: 6,
          ),
        ),
      },
    );

    return MaterialApp(
      // 2. Attach NotificationScope at the root builder
      builder: (context, child) => NotificationScope(
        controller: controller,
        child: child!,
      ),
      home: const HomeScreen(),
    );
  }
}

3. Dispatch Notifications #

Show notifications from UI widgets using NotificationScope.of(context) or contextlessly from BLoCs, Cubits, Repositories, or Services using controller.show():

// Context-aware dispatch from UI
NotificationScope.of(context).show(
  const AppNotification(
    title: 'Update Complete',
    message: 'System package updated successfully.',
    channelName: 'info',
  ),
);

// Error notification with button action
NotificationScope.of(context).show(
  AppNotification(
    title: 'Connection Failure',
    message: 'Unable to reach backend service. Retrying...',
    channelName: 'error',
    dismissDuration: null, // Stay until dismissed
    action: NotificationAction.button(
      label: 'RETRY',
      onPressed: () => print('Retry clicked'),
    ),
  ),
);

// Contextless dispatch (from BLoCs, Cubits, Repositories, or background services)
controller.show(
  const AppNotification(
    title: 'Background Sync',
    message: 'Data synchronization completed in background.',
    channelName: 'info',
  ),
);

// Safe contextless dispatch before first frame renders
controller.tryShow(
  const AppNotification(
    title: 'App Startup',
    message: 'Loaded user session.',
    channelName: 'info',
  ),
);

🎨 Common Recipes & Use Cases #

Custom Actions & Tap Behaviors #

Configure how users interact with the card surface or action buttons:

// Button Action
AppNotification(
  title: 'New Message',
  message: 'Sarah sent you a message.',
  channelName: 'info',
  action: NotificationAction.button(
    label: 'REPLY',
    onPressed: () => openChat(),
  ),
);

// Tap-To-Act (Entire card surface is tapable)
AppNotification(
  title: 'Security Alert',
  message: 'Unrecognized login attempt. Tap to review.',
  channelName: 'warning',
  tapBehavior: TapToAct(
    onTap: () => openSecuritySettings(),
    dismissOnAct: true,
  ),
);

// Tap-To-Expand (Expand long text on tap)
const AppNotification(
  title: 'Build Output',
  message: 'Detailed build log output spanning multiple lines...',
  channelName: 'info',
  tapBehavior: TapToExpand(),
);

Permanent & Sticky Notifications #

// Permanent card (no auto-dismiss countdown)
const AppNotification(
  title: 'Ongoing Download',
  message: 'Downloading asset pack (45%)...',
  channelName: 'info',
  dismissDuration: null,
);

// Sticky Alert (Disables swipe gestures for this specific card)
AppNotification(
  title: 'Critical Alert',
  message: 'System requires immediate attention.',
  channelName: 'error',
  dismissDuration: null,
  dragBehavior: const Disabled(),
);

🚀 Advanced Capabilities #

1. Intent-First Interactive Gestures #

FNQ provides sophisticated pointer and touch gesture handling configured at the queue level via dragBehavior and longPressDragBehavior:

  • Dismiss: Swipe to dismiss. Supports DismissZone.sideEdges or DismissZone.naturalDirection.
  • Reorder: Live-shifting drag reordering within the stack featuring Gravity-Well Hysteresis to eliminate pointer wobble and Selection Reticles for drop slot feedback.
  • Relocate: Drag a notification card to move it into another queue position.
  • ReorderAndRelocate: Reorder within the current stack, or drag past an escape threshold to relocate to a destination queue.
  • Disabled: Disables drag gestures.
NotificationQueue(
  position: QueuePosition.topRight,
  dragBehavior: const Dismiss(),
  longPressDragBehavior: ReorderAndRelocate.to(
    positions: {QueuePosition.bottomRight},
  ),
)

Tip

Relocation Auto-Registration: When target positions are defined in Relocate.to({...}) or ReorderAndRelocate.to(positions: {...}), FNQ automatically registers sibling target queues, clones source queue styling, and configures self-inclusion so cards can return home.


2. Notification Grouping (Bundling) #

Prevent screen flooding by collapsing multiple notifications from the same channel/group into an interactive stacked deck:

NotificationQueue(
  position: QueuePosition.topRight,
  groupingBehavior: QueueGroupingBehavior(
    enabled: true,
    maxBeforeGrouping: 2,         // Bundle after 2 notifications arrive
    maxStackedLayers: 3,          // Background card layers in bundle deck
    stackStepOffset: 6.0,         // Pixels offset per background card
    stackScaleMultiplier: 0.04,   // Scale reduction per background card
    enableGroupSwipeDismiss: true,// Swipe top card or whole bundle
  ),
)
  • Single-Card Swipe: Swiping the top card in a bundle dismisses only that notification and automatically surfaces the next hidden member.
  • Group Indicator Bar: Displays non-overlapping bundle indicators with dynamic padding and theme adaptation.

3. Priority Triage & Backpressure Eviction #

FNQ includes a semantic priority triage engine (NotificationPriority.low, normal, high, critical):

const AppNotification(
  title: 'Database Outage',
  message: 'Primary DB node unresponsive!',
  channelName: 'error',
  priority: NotificationPriority.critical,
);
  • Priority Auto-Sorting: Pending notifications auto-sort by priority so critical items display first.
  • Preemption Eviction: If a queue is full (maxStackSize) and a higher-priority notification arrives, FNQ automatically evicts the lowest-priority active card (emitting DismissReason.evicted), returns it to the pending queue, and displays the critical notification immediately.
  • Overflow Strategy: Configure QueueOverflowStrategy.discardOldest or discardNewest when pending queue capacity (maxPendingSize) is reached.

4. Dynamic Channel Parking #

Enable channels to dynamically learn new queue positions when users drag notifications across the screen:

final controller = NotificationController(
  enableDynamicChannelParking: true,
);

When enabled, relocating a card from topRight to bottomRight dynamically re-routes future notifications dispatched on that channel to bottomRight.


5. Observability & Event History Log #

Observe lifecycle events in real-time or query an in-memory LIFO history ring buffer:

// 1. Subscribe to live stream
controller.events.listen((event) {
  switch (event) {
    case NotificationQueued(:final notification):
      print('Displayed: ${notification.id}');
    case NotificationDismissed(:final notification, :final reason):
      print('Dismissed: ${notification.id} ($reason)');
    case NotificationRelocated(:final notification, :final from, :final to):
      print('Relocated from ${from.name} to ${to.name}');
    default:
      break;
  }
});

// 2. Query bounded history log (Opt-in via maxHistoryEntries)
final history = controller.getHistory(
  channelName: 'error',
  limit: 10,
);

Note

Set maxHistoryEntries: 0 (the default) for zero runtime memory or CPU overhead when history logging is not needed.


6. In-Place Runtime Reconfiguration #

Update queues, channels, dynamic parking, and history settings live without re-instantiating the controller or clearing active visible cards:

controller.reconfigure(
  queues: {
    const NotificationQueue(
      position: QueuePosition.topRight,
      maxStackSize: 5,
    ),
  },
  enableDynamicChannelParking: true,
);

7. Custom Transitions & Styling #

Choose from standard queue styles or provide custom transition animations and widget builders:

// Outlined Queue Style
const OutlinedQueueStyle(
  borderRadius: BorderRadius.all(Radius.circular(10)),
  borderWidth: 1.5,
  elevation: 4,
);

// Scale Transition
const ScaleTransitionStrategy(
  initialScale: 0.7,
  curve: Curves.backOut,
);

// Custom Animation Builder
NotificationQueue(
  position: QueuePosition.topCenter,
  transition: BuilderTransitionStrategy(
    (context, animation, position, child) => FadeTransition(
      opacity: animation,
      child: SlideTransition(
        position: Tween<Offset>(
          begin: const Offset(0, -0.5),
          end: Offset.zero,
        ).animate(animation),
        child: child,
      ),
    ),
  ),
);

🌐 Platform & Accessibility Support #

  • RTL Detection: Automatic text direction detection for Arabic, Persian, Hebrew, and Urdu.
  • Safe Area Integration: Automatic handling of notches, status bars, and navigation bars.
  • Desktop & Web: Mouse hover detection, desktop scroll wheel support, and progressive close button visibility (VisibleOnHover, AlwaysVisible, Hidden).
  • Haptics & Touch: Native touch gesture feedback and spring snapback physics.

📈 Migration Guide (v0.x to v1.0) #

Legacy v0.x API Modern v1.0 Architecture Description
FlutterNotificationQueue.configure(...) final controller = NotificationController(...) Pure Dart configuration owner
MaterialApp(builder: FlutterNotificationQueue.builder) NotificationScope(controller: controller, child: child!) Scoped widget tree overlay binding
NotificationWidget(message: '...').show() controller.show(AppNotification(message: '...')) Pure data payload intent
NotificationScope.of(context).show(...) NotificationScope.of(context).show(AppNotification(...)) Context-aware dispatching
FlutterNotificationQueue.events controller.events Stable broadcast stream

For full step-by-step details, see doc/migration_v0_4.md.


📄 License #

This project is licensed under the BSD 3-Clause License - see the LICENSE file for details.

1
likes
140
points
167
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

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.

Repository (GitHub)
View/report issues

Topics

#notification #overlay #toast #popup #ui

Funding

Consider supporting this project:

github.com

License

BSD-3-Clause (license)

Dependencies

flutter, logd, meta

More

Packages that depend on flutter_notification_queue