Skip to main content
Notification tray status management provides essential functionality for tracking whether users have seen their notifications. This enables accurate badge counts, notification indicators, and cross-device synchronization in your app.

Get Tray Status

Retrieve current seen status to show/hide notification badges

Mark as Seen

Update tray status when users view their notifications

Cross-Device Sync

Server-managed state ensures consistency across all user sessions

Realtime Events

Understand how events power real-time tray status updates

Key Features

Status Tracking

  • Global tray-level seen status - Track whether the entire notification tray has been viewed
  • Server-managed timestamps - Reliable lastTraySeenAt and lastTrayOccurredAt tracking
  • Cross-device consistency - Seen status synchronizes across all user devices

Real-time Updates

  • LiveObject observation - Immediate local updates when status changes
  • Optimal refresh strategy - Get updated status when returning to app
  • Smart polling - Optional background refresh strategies

Status Data Model

The notification tray status contains three key properties:
  • Properties
  • Usage Examples
  • State Logic
PropertyTypeDescription
isSeenBooleanWhether the tray has been seen since the last notification
lastTraySeenAtDatetimeTimestamp when the tray was last marked as seen
lastTrayOccurredAtDatetimeTimestamp when the most recent notification occurred

Core SDK Methods

  • Get Tray Status
  • Mark as Seen
token = client.notificationTray.getNotificationTraySeen().observe({ liveObject, error in
  
  guard let snapshot = liveObject.snapshot else { return }
  // Usage
  // snapshot.lastTraySeenAt
  // snapshot.lastTrayOccurredAt
  // snapshot.isSeen
})

Implementation Workflow

Typical Implementation Pattern
class NotificationTrayManager {
  constructor() {
    this.unsubscribe = null;
  }
  
  async initialize() {
    // 1. Get current status on app launch
    await this.subscribeToStatus();
    
    // 2. Setup app lifecycle listeners
    this.setupAppStateListeners();
  }
  
  async subscribeToStatus() {
    this.unsubscribe = await getNotificationTraySeen(
      ({ data: status, loading, error }) => {
        if (!loading && status) {
          this.updateUI(status);
        }
      }
    );
  }
  
  async openNotificationTray() {
    // 3. Mark as seen when tray is opened
    await this.markAsSeen();
  }
  
  async markAsSeen() {
    try {
      const timestamp = new Date().toISOString();
      await markTraySeen(timestamp);
      
      // UI updates automatically via LiveObject
    } catch (error) {
      console.error('Failed to mark as seen:', error);
    }
  }
  
  updateUI(status) {
    // Update badge visibility
    const showBadge = !status.isSeen;
    this.updateBadgeVisibility(showBadge);
    
    // Update last activity display
    this.updateLastActivity(status);
  }
  
  cleanup() {
    if (this.unsubscribe) {
      this.unsubscribe();
    }
  }
}
Handle App State Changes
class AppStateManager {
  constructor(trayManager) {
    this.trayManager = trayManager;
    this.setupListeners();
  }
  
  setupListeners() {
    // React Native
    AppState.addEventListener('change', this.handleAppStateChange);
    
    // Web
    document.addEventListener('visibilitychange', this.handleVisibilityChange);
  }
  
  handleAppStateChange = (nextAppState) => {
    if (nextAppState === 'active') {
      // App became active - refresh status
      this.trayManager.refreshStatus();
    }
  };
  
  handleVisibilityChange = () => {
    if (!document.hidden) {
      // Page became visible - refresh status
      this.trayManager.refreshStatus();
    }
  };
}
Robust Error Handling
class TrayStatusManager {
  async safeMarkAsSeen(retryCount = 0) {
    const maxRetries = 3;
    
    try {
      const timestamp = new Date().toISOString();
      await markTraySeen(timestamp);
      
      // Success - update UI
      this.updateBadgeVisibility(false);
      
    } catch (error) {
      console.error('Failed to mark tray as seen:', error);
      
      if (retryCount < maxRetries) {
        // Retry with exponential backoff
        const delay = Math.pow(2, retryCount) * 1000;
        setTimeout(() => {
          this.safeMarkAsSeen(retryCount + 1);
        }, delay);
      } else {
        // Max retries reached - handle gracefully
        this.handleMarkSeenFailure();
      }
    }
  }
  
  handleMarkSeenFailure() {
    // Update UI optimistically
    this.updateBadgeVisibility(false);
    
    // Show user feedback
    this.showErrorMessage('Failed to update notification status');
    
    // Schedule retry for later
    setTimeout(() => this.safeMarkAsSeen(), 30000);
  }
}

Refresh Strategies