image

20 Mar 2026

9K

35K

Building a Notification Center Widget with Swipe to Dismiss and Action Buttons in Flutter

Notification centers are a fundamental component of modern mobile applications, providing users with a centralized hub for important updates, alerts, and messages. A well-designed notification center enhances user experience by making information easily accessible and actionable. In Flutter, we can build a highly interactive notification center widget featuring "swipe to dismiss" functionality and customizable action buttons, allowing users to manage their notifications efficiently.

This article will guide you through the process of creating such a widget, covering the data model, UI implementation using Flutter's powerful widgets like Dismissible, and state management for a dynamic notification list.

Prerequisites

  • Basic understanding of Flutter and Dart.
  • Familiarity with StatelessWidget and StatefulWidget.
  • Knowledge of basic UI widgets like ListView, Row, Column, and Text.

1. Defining the Notification Data Model

First, let's create a simple data model to represent a single notification. This class will hold properties like ID, title, body, and an optional timestamp or read status.


class NotificationItem {
  final String id;
  final String title;
  final String body;
  final DateTime timestamp;
  bool isRead;

  NotificationItem({
    required this.id,
    required this.title,
    required this.body,
    required this.timestamp,
    this.isRead = false,
  });

  // Helper to generate unique IDs
  factory NotificationItem.generate({
    required String title,
    required String body,
  }) {
    return NotificationItem(
      id: DateTime.now().millisecondsSinceEpoch.toString(),
      title: title,
      body: body,
      timestamp: DateTime.now(),
    );
  }
}

2. Creating the Notification Tile Widget

Next, we'll build a widget to display a single notification item. This widget will be stateless and primarily responsible for presenting the notification's data in a visually appealing manner.


import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; // Add intl dependency to pubspec.yaml

// Placeholder for NotificationItem class from previous step
// class NotificationItem { ... }

class NotificationTile extends StatelessWidget {
  final NotificationItem notification;
  final VoidCallback? onMarkAsRead;
  final VoidCallback? onReply; // Example action

  const NotificationTile({
    Key? key,
    required this.notification,
    this.onMarkAsRead,
    this.onReply,
  }) : super(key: key);

  String _formatTimestamp(DateTime timestamp) {
    final now = DateTime.now();
    final today = DateTime(now.year, now.month, now.day);
    final notificationDate = DateTime(timestamp.year, timestamp.month, timestamp.day);

    if (notificationDate.isAtSameMomentAs(today)) {
      return DateFormat.jm().format(timestamp); // e.g., "3:30 PM"
    } else if (notificationDate.isAtSameMomentAs(today.subtract(const Duration(days: 1)))) {
      return "Yesterday";
    } else {
      return DateFormat.MMMd().format(timestamp); // e.g., "Oct 26"
    }
  }

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
      color: notification.isRead ? Colors.grey[200] : Colors.white,
      elevation: 2,
      child: Padding(
        padding: const EdgeInsets.all(12.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(
                  notification.isRead ? Icons.notifications_off : Icons.notifications_active,
                  color: notification.isRead ? Colors.grey : Theme.of(context).primaryColor,
                  size: 20,
                ),
                const SizedBox(width: 8),
                Expanded(
                  child: Text(
                    notification.title,
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      fontSize: 16,
                      color: notification.isRead ? Colors.grey : Colors.black87,
                    ),
                  ),
                ),
                Text(
                  _formatTimestamp(notification.timestamp),
                  style: const TextStyle(fontSize: 12, color: Colors.grey),
                ),
              ],
            ),
            const SizedBox(height: 8),
            Text(
              notification.body,
              style: TextStyle(
                fontSize: 14,
                color: notification.isRead ? Colors.grey[700] : Colors.black87,
              ),
              maxLines: 2,
              overflow: TextOverflow.ellipsis,
            ),
            if (onMarkAsRead != null || onReply != null) ...[
              const SizedBox(height: 12),
              Row(
                mainAxisAlignment: MainAxisAlignment.end,
                children: [
                  if (onMarkAsRead != null)
                    TextButton(
                      onPressed: onMarkAsRead,
                      child: Text(notification.isRead ? 'UNMARK' : 'MARK AS READ'),
                    ),
                  if (onReply != null)
                    const SizedBox(width: 8),
                  if (onReply != null)
                    TextButton(
                      onPressed: onReply,
                      child: const Text('REPLY'),
                    ),
                ],
              ),
            ],
          ],
        ),
      ),
    );
  }
}

3. Implementing Swipe to Dismiss with Dismissible

Flutter's Dismissible widget is perfect for adding swipe-to-dismiss functionality. We'll wrap our NotificationTile with Dismissible and configure its properties.


// This will be part of the main NotificationCenterScreen,
// but for demonstration, let's see how Dismissible wraps NotificationTile.

// Inside a StatefulWidget's build method, within a ListView.builder:
/*
  Dismissible(
    key: Key(notification.id), // Unique key is crucial for Dismissible
    direction: DismissDirection.endToStart, // Swipe from right to left
    background: Container(
      color: Colors.red,
      alignment: Alignment.centerRight,
      padding: const EdgeInsets.only(right: 20.0),
      child: const Icon(Icons.delete, color: Colors.white),
    ),
    onDismissed: (direction) {
      // Logic to remove the notification from the list
      // e.g., _removeNotification(notification.id);
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text("${notification.title} dismissed")),
      );
    },
    child: NotificationTile(
      notification: notification,
      onMarkAsRead: () {
        // Handle mark as read action
      },
      onReply: () {
        // Handle reply action
      },
    ),
  );
*/

4. Building the Notification Center Screen

Now, let's combine everything into a stateful widget, NotificationCenterScreen. This widget will manage a list of NotificationItems and display them using a ListView.builder, each wrapped in a Dismissible widget.


import 'package:flutter/material.dart';
// Don't forget to import 'package:intl/intl.dart'; if you're using it

// Placeholder for NotificationItem and NotificationTile
// class NotificationItem { ... }
// class NotificationTile { ... }

class NotificationCenterScreen extends StatefulWidget {
  const NotificationCenterScreen({Key? key}) : super(key: key);

  @override
  State createState() => _NotificationCenterScreenState();
}

class _NotificationCenterScreenState extends State {
  final List<NotificationItem> _notifications = [];

  @override
  void initState() {
    super.initState();
    _loadInitialNotifications();
  }

  void _loadInitialNotifications() {
    _notifications.add(NotificationItem.generate(
      title: 'New Message',
      body: 'John Doe sent you a message: "Hey, how are you doing?"',
    ));
    _notifications.add(NotificationItem.generate(
      title: 'Reminder: Meeting',
      body: 'Your daily stand-up meeting starts in 10 minutes.',
    ));
    _notifications.add(NotificationItem.generate(
      title: 'App Update Available',
      body: 'A new version of the app is available. Update now for new features!',
    ));
    _notifications.add(NotificationItem.generate(
      title: 'Friend Request',
      body: 'Jane Smith sent you a friend request.',
      isRead: true, // Example of a read notification
    ));
  }

  void _addNotification() {
    setState(() {
      _notifications.insert(0, NotificationItem.generate(
        title: 'New Notification ' + (_notifications.length + 1).toString(),
        body: 'This is a dynamically added notification. Swipe to dismiss!',
      ));
    });
  }

  void _removeNotification(String id) {
    setState(() {
      _notifications.removeWhere((item) => item.id == id);
    });
  }

  void _toggleNotificationReadStatus(String id) {
    setState(() {
      final index = _notifications.indexWhere((item) => item.id == id);
      if (index != -1) {
        _notifications[index].isRead = !_notifications[index].isRead;
      }
    });
  }

  void _replyToNotification(String id) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text("Reply action for notification ID: $id")),
    );
    // In a real app, this would navigate to a reply screen or open a dialog
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Notification Center'),
        actions: [
          IconButton(
            icon: const Icon(Icons.add_alert),
            onPressed: _addNotification,
          ),
        ],
      ),
      body: _notifications.isEmpty
          ? const Center(
              child: Text(
                'No new notifications. Great job!',
                style: TextStyle(fontSize: 16, color: Colors.grey),
              ),
            )
          : ListView.builder(
              itemCount: _notifications.length,
              itemBuilder: (context, index) {
                final notification = _notifications[index];
                return Dismissible(
                  key: Key(notification.id), // Unique key is essential
                  direction: DismissDirection.endToStart,
                  background: Container(
                    color: Colors.red,
                    alignment: Alignment.centerRight,
                    padding: const EdgeInsets.only(right: 20.0),
                    child: const Icon(Icons.delete, color: Colors.white),
                  ),
                  onDismissed: (direction) {
                    _removeNotification(notification.id);
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(content: Text("Notification '${notification.title}' dismissed")),
                    );
                  },
                  child: NotificationTile(
                    notification: notification,
                    onMarkAsRead: () => _toggleNotificationReadStatus(notification.id),
                    onReply: () => _replyToNotification(notification.id),
                  ),
                );
              },
            ),
    );
  }
}

5. Integrating into Your Application

To use this notification center, you simply need to navigate to NotificationCenterScreen from your main application widget. For example, in your main.dart:


import 'package:flutter/material.dart';
// Import your NotificationCenterScreen from its file

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Notification Center',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const NotificationCenterScreen(), // Set NotificationCenterScreen as the home screen
    );
  }
}

Conclusion

By leveraging Flutter's declarative UI and powerful widgets like Dismissible, we've successfully built a dynamic and interactive notification center. This widget provides users with convenient "swipe to dismiss" functionality and "action buttons" for quick interaction, significantly improving the overall user experience. You can further enhance this notification center by adding features like persistent storage for notifications, grouping notifications, or integrating with push notification services.

Remember that the key to building robust Flutter applications lies in proper state management and breaking down complex UIs into smaller, manageable widgets.

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