image

03 Apr 2026

9K

35K

Building a Notification Center Widget with Grouping, Swipe, Action Buttons, and Custom Icons in Flutter

A well-designed notification center is crucial for user engagement and experience in modern applications. It provides a centralized, organized, and interactive hub for users to manage their alerts. This article explores how to build a sophisticated notification center widget in Flutter, incorporating features like notification grouping, swipe-to-dismiss functionality, interactive action buttons, and custom icons for enhanced visual branding and context.

1. Understanding the Core Requirements

Before diving into implementation, let's define the key features we aim to integrate:

  • Grouping: Notifications from the same source (e.g., app, topic) should be grouped together to reduce clutter and improve readability.
  • Swipe Actions: Users should be able to interact with notifications via swipe gestures, typically to dismiss or archive them.
  • Action Buttons: Notifications should offer quick interactive buttons (e.g., "Reply," "Archive," "Mark as Read") directly within the notification tile.
  • Custom Icons: Displaying custom icons (e.g., app logos, specific alert types) helps users quickly identify the source or nature of a notification.

2. Designing the Notification Data Model

A robust data model is the foundation for our notification system. We'll define a class to represent individual notifications and another for their associated actions.


// lib/models/notification_model.dart

import 'package:flutter/foundation.dart'; // For VoidCallback

class NotificationAction {
  final String label;
  final VoidCallback onPressed;

  NotificationAction({required this.label, required this.onPressed});
}

class AppNotification {
  final String id;
  final String groupId; // Used for grouping notifications
  final String appName;
  final String title;
  final String body;
  final DateTime timestamp;
  final String? iconUrl; // URL for a custom icon (e.g., app logo)
  final List<NotificationAction>? actions; // List of actionable buttons

  AppNotification({
    required this.id,
    required this.groupId,
    required this.appName,
    required this.title,
    required this.body,
    required this.timestamp,
    this.iconUrl,
    this.actions,
  });
}

3. Building the Notification Tile Widget

Each notification will be represented by a NotificationTile widget. This widget will encapsulate the UI for a single notification, including its content, custom icon, swipe actions, and action buttons.


// lib/widgets/notification_tile.dart

import 'package:flutter/material.dart';
import '../models/notification_model.dart';

class NotificationTile extends StatelessWidget {
  final AppNotification notification;
  final VoidCallback onDismissed;

  NotificationTile({required this.notification, required this.onDismissed});

  @override
  Widget build(BuildContext context) {
    return Dismissible(
      key: Key(notification.id), // Unique key for Dismissible
      direction: DismissDirection.endToStart, // Swipe from right to left
      onDismissed: (direction) {
        onDismissed();
      },
      // Background shown during swipe for dismiss action
      background: Container(
        color: Colors.red,
        alignment: Alignment.centerRight,
        padding: EdgeInsets.only(right: 20),
        child: Icon(Icons.delete, color: Colors.white),
      ),
      child: Card(
        margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
        child: Padding(
          padding: const EdgeInsets.all(12.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                children: [
                  // Custom Icon
                  CircleAvatar(
                    backgroundImage: notification.iconUrl != null
                        ? NetworkImage(notification.iconUrl!)
                        : null,
                    // Fallback icon if no custom URL is provided
                    child: notification.iconUrl == null ? Icon(Icons.notifications_active) : null,
                    radius: 18,
                  ),
                  SizedBox(width: 12),
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          notification.appName,
                          style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
                        ),
                        Text(
                          notification.title,
                          style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
                        ),
                      ],
                    ),
                  ),
                  Text(
                    '${notification.timestamp.hour.toString().padLeft(2, '0')}:${notification.timestamp.minute.toString().padLeft(2, '0')}',
                    style: TextStyle(fontSize: 12, color: Colors.grey[600]),
                  ),
                ],
              ),
              SizedBox(height: 8),
              // Notification Body
              Padding(
                padding: const EdgeInsets.only(left: 50.0), // Align with app name/icon
                child: Text(notification.body),
              ),
              // Action Buttons
              if (notification.actions != null && notification.actions!.isNotEmpty)
                Padding(
                  padding: const EdgeInsets.only(top: 8.0, left: 50.0),
                  child: Row(
                    children: notification.actions!
                        .map((action) => Padding(
                              padding: const EdgeInsets.only(right: 8.0),
                              child: TextButton(
                                onPressed: action.onPressed,
                                child: Text(action.label),
                              ),
                            ))
                        .toList(),
                  ),
                ),
            ],
          ),
        ),
      ),
    );
  }
}

4. Implementing the Notification Center Screen with Grouping

The main screen will manage the list of notifications, group them, and render them using the NotificationTile widget.


// lib/screens/notification_center_screen.dart

import 'package:flutter/material.dart';
import '../models/notification_model.dart';
import '../widgets/notification_tile.dart';

class NotificationCenterScreen extends StatefulWidget {
  @override
  _NotificationCenterScreenState createState() => _NotificationCenterScreenState();
}

class _NotificationCenterScreenState extends State<NotificationCenterScreen> {
  List<AppNotification> _notifications = [
    // Sample Notifications
    AppNotification(
      id: '1',
      groupId: 'WhatsApp',
      appName: 'WhatsApp',
      title: 'New Message',
      body: 'John Doe: Hello there! How are you doing today?',
      timestamp: DateTime.now().subtract(Duration(minutes: 5)),
      iconUrl: 'https://cdn-icons-png.flaticon.com/512/124/124034.png',
      actions: [
        NotificationAction(label: 'Reply', onPressed: () => _handleAction('Reply to WhatsApp 1')),
        NotificationAction(label: 'Mark Read', onPressed: () => _handleAction('Mark WhatsApp 1 Read')),
      ],
    ),
    AppNotification(
      id: '2',
      groupId: 'WhatsApp',
      appName: 'WhatsApp',
      title: 'New Message',
      body: 'Jane Smith: Are you free this evening for a quick call?',
      timestamp: DateTime.now().subtract(Duration(minutes: 10)),
      iconUrl: 'https://cdn-icons-png.flaticon.com/512/124/124034.png',
    ),
    AppNotification(
      id: '3',
      groupId: 'Gmail',
      appName: 'Gmail',
      title: 'Project Update',
      body: 'Subject: Q3 Report Finalization - Please review the attached document.',
      timestamp: DateTime.now().subtract(Duration(hours: 1)),
      iconUrl: 'https://cdn-icons-png.flaticon.com/512/281/281769.png',
      actions: [
        NotificationAction(label: 'Archive', onPressed: () => _handleAction('Archive Gmail 1')),
        NotificationAction(label: 'Reply All', onPressed: () => _handleAction('Reply All Gmail 1')),
      ],
    ),
    AppNotification(
      id: '4',
      groupId: 'System',
      appName: 'System Update',
      title: 'Software Update Available',
      body: 'A new version of the OS is available for download. Tap to install.',
      timestamp: DateTime.now().subtract(Duration(days: 1)),
      iconUrl: 'https://cdn-icons-png.flaticon.com/512/189/189679.png',
      actions: [
        NotificationAction(label: 'Install Now', onPressed: () => _handleAction('Install System Update')),
      ],
    ),
     AppNotification(
      id: '5',
      groupId: 'WhatsApp',
      appName: 'WhatsApp',
      title: 'Missed Call',
      body: 'You missed a call from David Lee.',
      timestamp: DateTime.now().subtract(Duration(minutes: 30)),
      iconUrl: 'https://cdn-icons-png.flaticon.com/512/124/124034.png',
      actions: [
        NotificationAction(label: 'Call Back', onPressed: () => _handleAction('Call back David')),
      ],
    ),
  ];

  // Helper to group notifications by groupId
  Map<String, List<AppNotification>> _groupNotifications(List<AppNotification> notifications) {
    Map<String, List<AppNotification>> grouped = {};
    for (var notif in notifications) {
      if (!grouped.containsKey(notif.groupId)) {
        grouped[notif.groupId] = [];
      }
      grouped[notif.groupId]!.add(notif);
    }
    // Sort notifications within each group by timestamp (newest first)
    grouped.forEach((key, value) {
      value.sort((a, b) => b.timestamp.compareTo(a.timestamp));
    });
    return grouped;
  }

  // Handle notification dismissal
  void _dismissNotification(String notificationId) {
    setState(() {
      _notifications.removeWhere((notif) => notif.id == notificationId);
    });
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Notification dismissed!')),
    );
  }

  // Handle action button presses
  void _handleAction(String actionLabel) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Action: "$actionLabel" triggered!')),
    );
    // In a real app, this would trigger specific business logic
  }

  @override
  Widget build(BuildContext context) {
    final groupedNotifications = _groupNotifications(_notifications);
    final sortedGroupIds = groupedNotifications.keys.toList()
      ..sort((a, b) {
        // Sort groups by the timestamp of their newest notification
        final newestA = groupedNotifications[a]!.first.timestamp;
        final newestB = groupedNotifications[b]!.first.timestamp;
        return newestB.compareTo(newestA);
      });

    return Scaffold(
      appBar: AppBar(
        title: Text('Notification Center'),
        centerTitle: true,
      ),
      body: groupedNotifications.isEmpty
          ? Center(child: Text('No notifications to display!'))
          : ListView.builder(
              itemCount: sortedGroupIds.length,
              itemBuilder: (context, index) {
                String groupId = sortedGroupIds[index];
                List<AppNotification> group = groupedNotifications[groupId]!;

                return Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Padding(
                      padding: const EdgeInsets.all(12.0),
                      child: Text(
                        groupId, // Display the group identifier as a header
                        style: TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                          color: Theme.of(context).primaryColor,
                        ),
                      ),
                    ),
                    // Render each notification within the group
                    ...group.map((notification) => NotificationTile(
                      notification: notification,
                      onDismissed: () => _dismissNotification(notification.id),
                    )).toList(),
                    Divider(height: 1, thickness: 1, indent: 10, endIndent: 10), // Separator between groups
                  ],
                );
              },
            ),
    );
  }
}

5. Integrating into Your Flutter Application

To see the notification center in action, you can set it as the home screen of your Flutter app.


// main.dart

import 'package:flutter/material.dart';
import 'package:notification_center_demo/screens/notification_center_screen.dart'; // Adjust path as needed

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Notification Center Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: NotificationCenterScreen(),
    );
  }
}

Conclusion

By following these steps, you can build a highly functional and aesthetically pleasing notification center in Flutter. The modular design, leveraging Dismissible for swipe actions, flexible data models for action buttons and custom icons, and intelligent grouping logic, allows for a robust and user-friendly experience. This foundation can be further extended with features like read/unread states, persistent storage, real-time updates via websockets, and more advanced customization options.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is