Skip to main content

Query Notification Tray Item

Flexible Filtering

Query notifications with advanced filtering options for targeted retrieval

Pagination Support

Efficiently handle large notification sets with built-in pagination

Real-time Updates

Live collection updates for dynamic notification experiences

Multi-platform SDK

Consistent querying across iOS, Android, and Web platforms

Overview

The Query Notification Tray Item feature allows you to retrieve and filter notification tray items with powerful querying capabilities. This functionality is essential for building dynamic notification interfaces that can display relevant notifications based on user preferences, seen status, and notification types. Each notification tray item represents a specific event in your social network, with properties like eventType, actionType, and templatedText that define what triggered the notification and how it should be displayed to users.
For a complete reference of all event types, triggers, and message templates, see the Event Types & Message Templates section below.

Architecture Overview

Key Features

Seen Status Filtering

Filter notifications based on whether they have been seen or remain unread

Category-based Filtering

Query specific notification types such as posts, reactions, comments, or replies

Time-based Filtering

Retrieve notifications within specific date ranges or time periods

Actor-based Filtering

Filter notifications by specific users or user groups
  • Real-time Updates: Automatic UI refresh when new notifications arrive
  • Efficient Pagination: Built-in support for loading additional items
  • Smart Caching: Optimized data retrieval and local storage
  • State Management: Automatic handling of loading and error states
  • Lazy Loading: Load notifications on-demand to improve performance
  • Batch Operations: Efficient handling of multiple notification updates
  • Memory Management: Automatic cleanup of unused notification data
  • Network Optimization: Minimal API calls with intelligent caching

Query Configuration

Required Parameters:
  • limit: Maximum number of items to retrieve per page (default: 20, max: 100)
Optional Parameters:
  • includeDeleted: Include soft-deleted notifications (default: false)
  • sortBy: Sort order - lastOccurredAt or createdAt (default: lastOccurredAt)
  • sortDirection: asc or desc (default: desc)
Seen Status Filter:
  • seenStatus: seen, unseen, or all (default: all)
Category Filter:
  • categories: Array of notification categories to include
  • Available categories: post, poll, reaction, comment, reply, mention, follow
Time Range Filter:
  • startDate: Filter notifications after this timestamp
  • endDate: Filter notifications before this timestamp
Actor Filter:
  • actorIds: Array of user IDs to filter notifications by
Performance Settings:
  • enableRealTimeUpdates: Enable live collection updates (default: true)
  • cacheTimeout: Cache expiration time in seconds (default: 300)
UI Configuration:
  • loadingBehavior: immediate or lazy (default: immediate)
  • errorRetryCount: Number of retry attempts on failure (default: 3)

Pagination Implementation

class NotificationListViewController: UIViewController, UITableViewDataSource {
    private var notifications: [AmityNotificationTrayItem] = []
    private var liveCollection: AmityCollection<AmityNotificationTrayItem>?
    private var isLoading = false
    
    func loadMoreIfNeeded(at indexPath: IndexPath) {
        guard !isLoading,
              indexPath.row >= notifications.count - 5, // Load more when 5 items from end
              liveCollection?.hasNext == true else { return }
        
        isLoading = true
        liveCollection?.nextPage { [weak self] result in
            DispatchQueue.main.async {
                self?.isLoading = false
                switch result {
                case .success:
                    // Collection will update automatically through observer
                    break
                case .failure(let error):
                    self?.handlePaginationError(error)
                }
            }
        }
    }
    
    // MARK: - UITableViewDataSource
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return notifications.count + (liveCollection?.hasNext == true ? 1 : 0) // +1 for loading cell
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if indexPath.row >= notifications.count {
            // Return loading cell
            return tableView.dequeueReusableCell(withIdentifier: "LoadingCell", for: indexPath)
        }
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "NotificationCell", for: indexPath)
        configureCell(cell, with: notifications[indexPath.row])
        return cell
    }
}

Best Practices

Efficient Querying Strategies

Reasonable Page Sizes

Use page sizes between 20-50 items to balance performance and user experience

Targeted Filtering

Apply specific filters to reduce data transfer and improve response times

Smart Caching

Leverage built-in caching mechanisms to minimize unnecessary network requests

Progressive Loading

Implement progressive loading for better perceived performance

Performance Guidelines

// ✅ Good: Reasonable page size with specific filtering
const optimizedQuery = {
    limit: 25,
    seenStatus: 'unseen',
    categories: ['post', 'reaction'],
    startDate: new Date(Date.now() - 86400000) // Last 24 hours
};

// ❌ Avoid: Too large page size without filtering
const inefficientQuery = {
    limit: 200, // Too large
    seenStatus: 'all', // Too broad
    // No time filtering - queries all historical data
};

Robust Error Management

Network Failures

Implement retry logic with exponential backoff for network-related errors

Rate Limiting

Handle rate limiting gracefully with appropriate user feedback

Data Validation

Validate query parameters before sending requests

Graceful Degradation

Provide fallback experiences when queries fail

Implementation Pattern

class NotificationErrorHandler {
    func handleQueryError(_ error: Error, retryAction: @escaping () -> Void) {
        switch error {
        case AmityError.networkError:
            showRetryOption(retryAction)
        case AmityError.rateLimited:
            showRateLimitMessage()
        case AmityError.invalidQuery:
            logQueryValidationError(error)
            showGenericError()
        default:
            showGenericError()
        }
    }
    
    private func showRetryOption(_ retryAction: @escaping () -> Void) {
        let alert = UIAlertController(
            title: "Connection Error",
            message: "Unable to load notifications. Try again?",
            preferredStyle: .alert
        )
        
        alert.addAction(UIAlertAction(title: "Retry", style: .default) { _ in
            retryAction()
        })
        
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        
        // Present alert
    }
}

Lifecycle Best Practices

  • Proper Disposal: Always dispose live collections when views are destroyed
  • Weak References: Use weak references to prevent retain cycles
  • Background Processing: Handle collection updates on appropriate threads
  • Resource Cleanup: Clean up observers and subscriptions appropriately

Implementation Examples

class NotificationViewController: UIViewController {
    private var liveCollection: AmityCollection<AmityNotificationTrayItem>?
    private var collectionToken: AmityNotificationToken?
    
    deinit {
        // Clean up resources
        collectionToken?.invalidate()
        liveCollection = nil
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        
        // Pause updates when view is not visible
        if isMovingFromParent {
            collectionToken?.invalidate()
        }
    }
}

Use Cases

Notification Feed

Display a chronological list of all user notifications with filtering optionsImplementation:
  • Use basic query with time-based sorting
  • Enable real-time updates for live experience
  • Implement pagination for performance

Unread Badge Counter

Show count of unseen notifications for UI badge indicatorsImplementation:
  • Query with seenStatus: 'unseen'
  • Use count from collection metadata
  • Refresh on app foreground/resume

Category-specific Views

Create separate views for different notification typesImplementation:
  • Filter by specific categories
  • Create dedicated UI for each type
  • Optimize queries for targeted content

User Activity Timeline

Show notifications from specific users or user groupsImplementation:
  • Use actor-based filtering
  • Combine with time range filters
  • Enable cross-referencing with user profiles

Event Types

This section provides a comprehensive reference of all supported notification event types, their triggers, and the message templates displayed in the notification tray.
Understanding these event types and message templates is essential for properly rendering and handling notifications in your application. Each event has specific conditions that trigger it and corresponding message formats.

Complete Event Reference Table

Event TypeTrigger ConditionMessage Template
Post (Text, Image, Video)Bob & Alice are members of the same community
Bob creates a post in that community
→ Alice sees notification
Single notification:
Bob posted in {{communityDisplayName}}

Grouped (2 actors):
{{displayName_1}} and {{displayName_2}} posted in {{communityDisplayName}}

Grouped (3+ actors):
{{displayName_1}} and {{number}} others posted in {{communityDisplayName}}

Note: Posts in the same community within the same day are grouped
PollBob & Alice are members of the same community
Bob starts a poll in that community
→ Alice sees notification
Bob started a poll in {{communityDisplayName}}
CommentBob comments on Alice’s post
→ Alice sees notification
Community Post:
Bob commented on your post in {{communityDisplayName}}

Alice’s User Feed:
Bob commented on your post on your feed

Another User’s Feed:
Bob commented on your post on {{targetUserDisplayName}} feed
ReplyBob replies to Alice’s comment
→ Alice sees notification
Community Comment:
Bob replied to your comment in {{communityDisplayName}}

Alice’s Feed:
Bob replied to your comment on your feed

Bob’s Feed:
Bob replied to your comment on their feed

Charlie’s Feed:
Bob replied to your comment on Charlie feed

Error Handling

Network Connectivity Issues:
  • Error: Connection timeout or network unavailable
  • Response: Show offline indicator and retry options
  • Recovery: Implement exponential backoff retry strategy
Invalid Query Parameters:
  • Error: Malformed query or invalid filter values
  • Response: Log validation errors and use fallback query
  • Recovery: Validate parameters before query execution
Rate Limiting:
  • Error: Too many requests in short time period
  • Response: Display rate limit message to user
  • Recovery: Implement request throttling and queue management
Server Errors:
  • Error: Internal server errors or maintenance
  • Response: Show maintenance message with status updates
  • Recovery: Implement graceful degradation and status checking
class NotificationQueryService {
    private let maxRetryAttempts = 3
    private var retryCount = 0
    
    func executeQueryWithRetry(query: AmityNotificationTrayQuery, completion: @escaping (Result<AmityCollection<AmityNotificationTrayItem>, Error>) -> Void) {
        
        let executeQuery = { [weak self] in
            self?.repository.getNotificationTrayItems(query: query) { result in
                switch result {
                case .success(let collection):
                    self?.retryCount = 0 // Reset on success
                    completion(.success(collection))
                case .failure(let error):
                    self?.handleQueryFailure(error, query: query, completion: completion)
                }
            }
        }
        
        executeQuery()
    }
    
    private func handleQueryFailure(_ error: Error, query: AmityNotificationTrayQuery, completion: @escaping (Result<AmityCollection<AmityNotificationTrayItem>, Error>) -> Void) {
        
        retryCount += 1
        
        if retryCount <= maxRetryAttempts && shouldRetry(error) {
            let delay = pow(2.0, Double(retryCount)) // Exponential backoff
            
            DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
                self?.executeQueryWithRetry(query: query, completion: completion)
            }
        } else {
            retryCount = 0
            completion(.failure(error))
        }
    }
    
    private func shouldRetry(_ error: Error) -> Bool {
        // Determine if error is retryable
        if case AmityError.networkError = error {
            return true
        }
        return false
    }
}