Skip to main content

Get Notification Tray Seen

Unseen Status Detection

Determine whether there are unseen notifications in the tray

Cross-device Sync

Manage seen status across multiple client devices

Overview

The getNotificationTraySeen() method allows your app to determine whether there are unseen notifications by retrieving the current isSeen status of the notification tray. This is particularly useful for reflecting read/unread indicators in the UI—such as toggling a bell icon badge—based on whether new notifications have arrived since the tray was last viewed.
The seen status is managed on the server and may be affected by actions from other devices. However, the state is not updated via real-time events, and thus requires manual refresh to stay current.

Refresh Strategies

Polling should be avoided unless absolutely necessary due to server rate limiting risks and battery drain concerns.
If polling is required:
  • Minimum interval: 120 seconds or more
  • Use case: Critical real-time applications only
  • Implementation: Include proper error handling and exponential backoff
class NotificationPollingManager {
    private let repository = AmityNotificationTrayRepository()
    private var timer: Timer?
    private let pollingInterval: TimeInterval = 120.0 // 2 minutes minimum
    
    func startPolling() {
        stopPolling() // Ensure no duplicate timers
        
        timer = Timer.scheduledTimer(withTimeInterval: pollingInterval, repeats: true) { [weak self] _ in
            self?.pollTrayStatus()
        }
    }
    
    func stopPolling() {
        timer?.invalidate()
        timer = nil
    }
    
    private func pollTrayStatus() {
        repository.getNotificationTraySeen { [weak self] result in
            switch result {
            case .success(let trayData):
                DispatchQueue.main.async {
                    self?.updateBadgeIndicator(!trayData.isSeen)
                }
            case .failure(let error):
                // Consider exponential backoff on errors
                print("Polling error: \(error)")
            }
        }
    }
    
    deinit {
        stopPolling()
    }
}

Cross-device Update Behavior

Understanding how seen status synchronizes across different scenarios:

Same Client Updates

When markNotificationTraySeen() is called on the same client, the isSeen value updates immediately if LiveObject is being observed

Cross-device Updates

No real-time sync between devices. Manual getNotificationTraySeen() call required to retrieve updated state

Scenario 1: Same Device Update

Scenario 2: Cross-device Update

class CrossDeviceSyncManager {
    private let repository = AmityNotificationTrayRepository()
    
    // Call this when app becomes active
    func handleAppDidBecomeActive() {
        refreshFromServer()
    }
    
    // Call this when returning from background
    func handleAppWillEnterForeground() {
        refreshFromServer()
    }
    
    private func refreshFromServer() {
        repository.getNotificationTraySeen { [weak self] result in
            DispatchQueue.main.async {
                switch result {
                case .success(let trayData):
                    self?.syncLocalState(with: trayData)
                case .failure(let error):
                    self?.handleSyncError(error)
                }
            }
        }
    }
    
    private func syncLocalState(with trayData: AmityNotificationTraySeen) {
        // Update UI badges, counters, etc.
        NotificationCenter.default.post(
            name: .notificationTrayStateChanged,
            object: nil,
            userInfo: ["hasUnseen": !trayData.isSeen]
        )
    }
}

Error Handling

Network Connectivity

Handle offline scenarios gracefully with cached states

Rate Limiting

Implement exponential backoff for frequent API calls

Authentication

Handle token expiration and re-authentication flows

Server Errors

Provide fallback UI states for server-side issues
class NotificationTrayErrorHandler {
    private let repository = AmityNotificationTrayRepository()
    private var retryCount = 0
    private let maxRetries = 3
    
    func getNotificationTraySeenWithRetry(completion: @escaping (Result<AmityNotificationTraySeen, Error>) -> Void) {
        repository.getNotificationTraySeen { [weak self] result in
            switch result {
            case .success(let trayData):
                self?.retryCount = 0
                completion(.success(trayData))
                
            case .failure(let error):
                self?.handleError(error, completion: completion)
            }
        }
    }
    
    private func handleError(_ error: Error, completion: @escaping (Result<AmityNotificationTraySeen, Error>) -> Void) {
        guard retryCount < maxRetries else {
            completion(.failure(error))
            return
        }
        
        let delay = pow(2.0, Double(retryCount)) // Exponential backoff
        retryCount += 1
        
        DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
            self?.getNotificationTraySeenWithRetry(completion: completion)
        }
    }
}

Best Practices

Timing

When to Call
  • App launch or resume
  • Tray UI becomes visible
  • User pull-to-refresh action
  • After significant user inactivity

Frequency

Call Frequency
  • On-demand only (recommended)
  • Minimum 120s intervals if polling
  • Avoid excessive API calls
  • Implement rate limiting client-side
  • Cache Results: Store last known state locally for immediate UI updates
  • Batch Operations: Combine with other notification-related API calls when possible
  • Background Processing: Perform network calls off the main thread
  • Memory Management: Properly dispose of observers and callbacks
  • Visual Indicators: Use clear badges, dots, or colors for unseen states
  • Loading States: Show appropriate loading indicators during refresh
  • Error States: Provide meaningful error messages and retry options
  • Accessibility: Ensure notification states are accessible to screen readers
  • Authentication: Verify user authentication before making calls
  • Permissions: Request appropriate notification permissions
  • Data Privacy: Handle notification data according to privacy policies
  • Logging: Avoid logging sensitive notification content