# How to Build a Custom Notification Center in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 20-30 min
- Compatibility: FlutterFlow Free+ (Custom Widget needed for swipe-to-dismiss)
- Last updated: March 2026

## TL;DR

Build an in-app notification center using a Firestore subcollection per user — users/{uid}/notifications with fields title, body, type, isRead, timestamp, and actionUrl. Display notifications in a reversed-order ListView with bold text and a blue dot for unread items. Add a bell icon in the AppBar with a badge showing unread count from a Firestore count query. Tap a notification to mark it read and navigate to the page specified in actionUrl. Swipe-to-dismiss requires a Custom Widget wrapping each item in Dismissible.

## In-app notification center with Firestore and real-time badges

Push notifications bring users back to your app, but they need an in-app notification center to review past alerts. This tutorial builds one: a Firestore subcollection stores notifications per user, a bell icon in the AppBar shows the unread count as a red badge, and a dedicated page lists all notifications with read/unread styling, tap-to-navigate, and swipe-to-dismiss. The unread count query runs in real-time so the badge updates instantly when new notifications arrive.

## Before you start

- A FlutterFlow project with Firebase/Firestore connected
- Firebase Authentication enabled with users already signing in
- Basic understanding of Backend Queries and Conditional Visibility
- For swipe-to-dismiss: FlutterFlow Pro plan (Custom Widget required)

## Step-by-step guide

### 1. Create the Firestore notifications subcollection

In Firestore, each user gets their own notifications subcollection at users/{uid}/notifications. Create fields: title (String), body (String), type (String — 'order', 'message', 'system', 'promo'), isRead (Boolean, default false), timestamp (Timestamp), and actionUrl (String — in-app route like '/orders/abc123'). Set Firestore rules: match /users/{uid}/notifications/{notifId} { allow read, update: if request.auth.uid == uid; }. Only Cloud Functions should create notifications (admin SDK bypasses rules).

**Expected result:** Each user has a notifications subcollection with proper security rules.

### 2. Add the bell icon with unread count badge in the AppBar

In your main page's AppBar, place a Stack widget in the actions area. Bottom layer: IconButton with Icons.notifications_outlined, On Tap navigates to NotificationsPage. Top layer: a Positioned Container (18×18, circular, red background) in the top-right corner containing a Text widget with the unread count. Bind the count via a Backend Query on users/{currentUser.uid}/notifications where isRead == false, using the query result count. Wrap the badge in Conditional Visibility: hide when count == 0. Set Single Time Query to OFF for real-time updates.

**Expected result:** A bell icon with a red badge showing the unread count appears in the AppBar. Badge hides when all are read.

### 3. Build the notifications ListView with read/unread conditional styling

Create NotificationsPage with a ListView bound to Backend Query: users/{currentUser.uid}/notifications, ordered by timestamp descending, limit 20, infinite scroll ON. Each item is a Component with a Row: left has a small Container (8×8, circular, blue — Conditional Visibility: isRead == false), then an Icon that varies by type (Icons.shopping_bag for 'order', Icons.message for 'message', Icons.campaign for 'promo', Icons.info for 'system' — use Conditional Value), then a Column with title Text (Conditional Style: fontWeight bold when !isRead, normal when isRead) and body Text in secondaryText color plus a timestamp Text. Set the Row background: light blue tint (#E3F2FD) when !isRead, white when isRead via Conditional Style.

**Expected result:** Notifications display with bold unread items showing a blue dot and tinted background. Read items appear dimmer.

### 4. Add tap-to-navigate that marks notification as read

On each notification Component, add an On Tap action trigger. Action Flow: first Update Document → set isRead = true on this notification's document reference. Then navigate using the actionUrl field: add a Conditional Action chain — if type == 'order' Navigate To OrderDetailPage (pass the ID parsed from actionUrl), if type == 'message' Navigate To ChatPage, etc. The unread badge decrements automatically because the real-time count query detects the isRead change. For dynamic routing without conditionals: use a Custom Action with Navigator.pushNamed(context, notification.actionUrl).

**Expected result:** Tapping a notification marks it read (dot disappears, text unbolds) and navigates to the relevant page.

### 5. Add Mark All Read button and swipe-to-dismiss

In the NotificationsPage AppBar, add an IconButton (Icons.done_all). On Tap Action Flow: query all notifications where isRead == false for the current user, then loop through results calling Update Document isRead = true on each. For better performance, use a Custom Action with Firestore WriteBatch to update all in a single batch write. For swipe-to-dismiss: create a Custom Widget that wraps each notification Component in Flutter's Dismissible widget with direction: DismissDirection.endToStart, a green background with a checkmark icon, and onDismissed calling an Action Parameter callback that marks isRead = true.

```
// Custom Widget: DismissibleNotification
Dismissible(
  key: Key(widget.notificationId),
  direction: DismissDirection.endToStart,
  background: Container(
    color: Colors.green,
    alignment: Alignment.centerRight,
    padding: const EdgeInsets.only(right: 16),
    child: const Icon(Icons.done, color: Colors.white),
  ),
  onDismissed: (_) => widget.onDismiss?.call(widget.notificationId),
  child: widget.child,
)
```

**Expected result:** Swiping a notification slides it away and marks it read. Mark All Read clears all unread badges at once.

## Complete code example

File: `Notification Center Architecture`

```text
Firestore Data Model:
└── users/{uid}/notifications/{notifId}
    ├── title: String ("Your order shipped")
    ├── body: String ("Order #1234 is on its way")
    ├── type: String ("order" | "message" | "system" | "promo")
    ├── isRead: Boolean (false)
    ├── timestamp: Timestamp
    └── actionUrl: String ("/orders/abc123")

AppBar Bell Icon (any page):
├── Stack
│   ├── IconButton (Icons.notifications_outlined)
│   │   └── On Tap → Navigate To: NotificationsPage
│   └── Positioned (top: 0, right: 0)
│       └── Container (18x18, circular, #FF0000) [Cond. Vis: count > 0]
│           └── Text (unread count, white, fontSize: 10, fontWeight: bold)
└── Backend Query: notifications where isRead==false (count, real-time)

NotificationsPage:
├── AppBar
│   ├── Title: "Notifications"
│   └── Action: IconButton (Icons.done_all) → batch update isRead=true
└── ListView (query: notifications, orderBy: timestamp DESC, limit: 20)
    └── NotificationRow Component (per item)
        ├── Row (bg: isRead ? #FFFFFF : #E3F2FD)
        │   ├── Container (8x8, circular, blue) [Cond. Vis: !isRead]
        │   ├── SizedBox (w: 8)
        │   ├── Icon (conditional by type field)
        │   ├── SizedBox (w: 12)
        │   ├── Expanded → Column (crossAxis: start)
        │   │   ├── Text (title, bold if !isRead)
        │   │   ├── Text (body, secondaryText, maxLines: 2)
        │   │   └── Text (relative timestamp, caption)
        │   └── Icon (Icons.chevron_right, gray)
        └── On Tap → Update isRead=true → Navigate by actionUrl
```

## Common mistakes

- **Querying all notifications without pagination** — Active users accumulate hundreds of notifications over months. Loading all at once causes slow page loads and high Firestore read costs ($0.06 per 100K reads). Fix: Set limit to 20 and enable infinite scroll on the Backend Query. Run a scheduled Cloud Function to delete notifications older than 30 days.
- **Using Single Time Query for the badge count** — A one-time query only checks on page load. New notifications arriving while the user is in the app don't update the badge until they navigate away and return. Fix: Set Single Time Query to OFF on the bell badge count query. The real-time stream updates the badge instantly when the Cloud Function creates a new notification.
- **Marking all read with individual update calls in a loop** — If the user has 50 unread notifications, this fires 50 separate Firestore writes — slow, expensive, and may hit rate limits. Fix: Use a Custom Action with Firestore WriteBatch: batch.update(docRef, {'isRead': true}) for each, then batch.commit() to send all updates in one network round-trip.

## Best practices

- Use a subcollection per user (users/{uid}/notifications) not a global collection with userId filter — better security rules and query performance
- Keep the badge count query real-time (Single Time Query: OFF) for instant badge updates
- Include a type field for icon differentiation and routing logic
- Paginate with limit 20 + infinite scroll — never load unbounded notification lists
- Archive old notifications (30+ days) via a scheduled Cloud Function to control costs
- Use Firestore WriteBatch for mark-all-read to batch updates into a single operation
- Test with 100+ notifications to verify scrolling performance before deploying

## Frequently asked questions

### How do I create notifications from a Cloud Function?

Use admin.firestore().collection('users').doc(recipientUid).collection('notifications').add({title, body, type, isRead: false, timestamp: admin.firestore.Timestamp.now(), actionUrl}). The user's real-time query picks it up instantly and the badge updates.

### Can I also send push notifications alongside the in-app center?

Yes. When the Cloud Function creates the Firestore notification document, also call admin.messaging().send({token: userFcmToken, notification: {title, body}}). Push brings users back to the app; the in-app center lets them review history.

### How do I show relative timestamps like '5 min ago'?

Create a Custom Function: calculate the difference between now and the notification timestamp. Return 'Just now' for <1 min, 'X min ago' for <60 min, 'X hours ago' for <24 hours, or a formatted date string for older.

### Should I keep notifications forever or delete old ones?

Delete notifications older than 30-90 days via a scheduled Cloud Function. Firestore charges per document read — thousands of stale notifications still cost money when queried. Archive to a separate collection if you need an audit trail.

### How do I group notifications by date in the list?

Use a Custom Function that compares each notification's date to the previous item. When the date changes, insert a section header ('Today', 'Yesterday', 'March 15'). Implement this by generating items in a Custom Widget that interleaves headers with notification rows.

### Can RapidDev help build a full notification system?

Yes. A production notification system with push, email, in-app channels, user preferences, delivery scheduling, and analytics requires Cloud Functions and FCM setup beyond the visual builder. RapidDev can architect the full system.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-build-a-custom-notification-center-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-build-a-custom-notification-center-in-flutterflow
