Documentation Index Fetch the complete documentation index at: https://learn.social.plus/llms.txt
Use this file to discover all available pages before exploring further.
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
Property Type Description 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
// Badge visibility logic
const showBadge = ! status . isSeen ;
// Time-based logic
const hasNewNotifications = status . lastTrayOccurredAt > status . lastTraySeenAt ;
// User engagement tracking
const timeSinceLastSeen = Date . now () - status . lastTraySeenAt ;
Common Status Patterns // Check if user has unseen notifications
const hasUnseenNotifications = ! isSeen ;
// Determine badge count
const badgeCount = isSeen ? 0 : getUnreadItemCount ();
// Show last activity
const lastActivity = lastTrayOccurredAt > lastTraySeenAt
? 'New notifications available'
: 'All caught up' ;
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
})
Task { @MainActor in
try await client. notificationTray . markSeen ()
}
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 ();
}
}
}
App Lifecycle Integration
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
On-Demand (Recommended)
Periodic Refresh
Smart Refresh
When to Use
App launch and foreground transitions
User-initiated refresh actions
Navigation to notification tray
After marking notifications as seen
Implementation class OnDemandRefresh {
async refreshOnAppLaunch () {
// Get status when app starts
await this . getStatus ();
}
async refreshOnTrayOpen () {
// Refresh before showing tray
await this . getStatus ();
// Mark as seen after tray is displayed
await this . markAsSeen ();
}
async refreshOnPullToRefresh () {
// User-initiated refresh
await this . getStatus ();
}
}
Benefits
Reduces server load
Provides up-to-date status when needed
Better user experience with immediate feedback
No unnecessary background requests
When to Use
Long-running background applications
Critical notification systems
When real-time accuracy is essential
Implementation class PeriodicRefresh {
constructor () {
this . interval = null ;
this . REFRESH_INTERVAL = 120000 ; // 2 minutes minimum
}
startPeriodicRefresh () {
this . interval = setInterval (() => {
this . refreshStatus ();
}, this . REFRESH_INTERVAL );
}
stopPeriodicRefresh () {
if ( this . interval ) {
clearInterval ( this . interval );
this . interval = null ;
}
}
async refreshStatus () {
try {
await getNotificationTraySeen ();
} catch ( error ) {
console . error ( 'Periodic refresh failed:' , error );
}
}
}
⚠️ Important Considerations
Minimum interval: 120 seconds
Risk of server rate limiting
Increased battery usage
Should be used sparingly
Adaptive Strategy class SmartRefresh {
constructor () {
this . isAppActive = true ;
this . hasActiveNotifications = false ;
this . lastRefresh = null ;
}
async smartRefresh () {
const now = Date . now ();
const timeSinceLastRefresh = now - ( this . lastRefresh || 0 );
// Only refresh if enough time has passed
if ( timeSinceLastRefresh < 60000 ) return ; // 1 minute
// Refresh based on app state
if ( this . shouldRefresh ()) {
await this . refreshStatus ();
this . lastRefresh = now ;
}
}
shouldRefresh () {
return this . isAppActive && (
this . hasActiveNotifications ||
this . userRecentlyActive ()
);
}
userRecentlyActive () {
// Check if user has interacted with app recently
return this . lastUserInteraction > Date . now () - 300000 ; // 5 minutes
}
}
Notification Overview Complete guide to notification tray implementation
Notification Items Query and manage individual notification items
Notification Events Reference for notification event types and triggers
Realtime Events Learn about event-driven real-time communication