Presence
In Signaling solutions, it is often important to know a user's current online status. For example, in instant messaging, chat applications, and online collaboration tools, users need to see the availability of their contacts. This information is typically displayed as a status message or icon next to a user's name. Presence features in Signaling SDK enable you to monitor join, leave, and status change notifications for users in a channel. Using Presence, you can:
- Get a list of users currently in a channel and their temporary status data.
- Get a list of channels a specified user has joined or is subscribed to.
- Get, set, or remove user statuses.
- Receive real-time event notifications when users join or leave specified channels.
- Receive user status change event notifications in real time.
Understand the tech
Presence provides real-time information about the availability, and the current status of users, for effective communication and collaboration. It enables you to retrieve a list of users in a channel, or to query the list of channels for a specific user. The following figure illustrates how you integrate presence features into your app.
Presence workflow
Prerequisites
Ensure that you have integrated the Signaling SDK in your project, and implemented the framework functionality from the SDK quickstart page.
Implement presence features
Using presence, you can implement the following features:
Get channel users
To obtain a list of online users in a channel, call getOnlineUsers
. Depending on your parameter settings, this method returns a list of online user IDs in a channel and their temporary status data, or just the number of online users in the channel. You do not need to join a channel to call this method. This method is applicable to both message channels and stream channels. Use the channelType
parameter to specify the channel type.
After obtaining the initial online users list, update it in real time through didReceivePresenceEvent
event notifications.
Refer to the following sample code to query the list of online users in a channel and their current status:
- Swift
- Objective-C
let presenceOptions = AgoraRtmPresenceOptions()presenceOptions.includeState = truepresenceOptions.includeUserId = true// Retrieve the user state of the current channelrtm.getPresence()?.getOnlineUsers(channelName: "your_channel", channelType: .message, options: presenceOptions) { response, errorInfo in if errorInfo == nil { print("getOnlineUsers success!!") let userCount = response?.totalOccupancy ?? 0 let userStates = response?.userStateList ?? [] // You can process userCount and userStates here } else { print("getOnlineUsers failed, errorCode \(errorInfo!.errorCode), reason \(errorInfo!.reason)") }}
AgoraRmPresenceOptions* presence_opt = [[AgoraRtmPresenceOptions alloc] init];presence_opt.includeState = true;presence_opt.includeUserId = true;[[rtm getPresence] getOnlineUsers:@"your_channel" channelType:AgoraRtmChannelTypeMessage options:presence_opt completion:^(AgoraRtmgetOnlineUsersResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) { if (errorInfo == nil) { NSLog(@"getOnlineUsers success!!"); int user_count = response.totalOccupancy; NSArray<AgoraRtmUserState *> * user_states = response.userStateList; } else { NSLog(@"getOnlineUsers failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason); }}];
The getOnlineUsers
method retrieves one page of data at a time. Each page contains up to 100 online users. If the channel has more than 100 users, the response.nextPage
field contains a bookmark for the next page. After each query, check response.nextPage
to determine if there is more data. To retrieve the next page, set the page
field in AgoraRtmPresenceOptions
to the value of response.nextPage
. Repeat this process until response.nextPage
is empty. Refer to the following code:
- Swift
- Objective-C
let presenceOptions = AgoraRtmPresenceOptions()presenceOptions.includeState = truepresenceOptions.includeUserId = truepresenceOptions.page = "your_Next_Page_Bookmark"// Retrieve the user state of the current channelrtm.getPresence()?.getOnlineUsers("your_channel", channelType: .message, options: presenceOptions) { response, errorInfo in if errorInfo == nil { print("getOnlineUsers success!!") let userCount = response?.totalOccupancy ?? 0 let nextPage = response?.nextPage ?? "" let userStates = response?.userStateList ?? [] // You can process userCount, nextPage, and userStates here } else { print("getOnlineUsers failed, errorCode \(errorInfo!.errorCode), reason \(errorInfo!.reason)") }}
AgoraRtmPresenceOptions* presence_opt = [[AgoraRtmPresence alloc] init];presence_opt.includeState = true;presence_opt.includeUserId = true;presence_opt.page = @"your_Next_Page_Bookmark";[[rtm getPresence] getOnlineUsers:@"your_channel" channelType:AgoraRtmChannelTypeMessage options:presence_opt completion:^(AgoraRtmgetOnlineUsersResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) { if (errorInfo == nil) { NSLog(@"getOnlineUsers success!!"); int user_count = response.totalOccupancy; NSString* next_page = response.nextPage; NSArray<AgoraRtmUserState *> * user_states = response.userStateList; } else { NSLog(@"getOnlineUsers failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason); }}];
When there is a large number of users in a channel, you may only care about the total number of online users, and not their identities or temporary status. To get the total channel occupancy, set includeState
and includeUserId
in AgoraRtmPresenceOptions
to false
.
- Swift
- Objective-C
let presenceOptions = AgoraRtmPresenceOptions()presenceOptions.includeState = falsepresenceOptions.includeUserId = false// Retrieve the user state of the current channelrtm.getPresence()?.getOnlineUsers(channelName: "your_channel", channelType: .message, options: presenceOptions) { response, errorInfo in if errorInfo == nil { print("getOnlineUsers success!!") let userCount = response?.totalOccupancy ?? 0 } else { print("getOnlineUsers failed, errorCode \(errorInfo!.errorCode), reason \(errorInfo!.reason)") }}
AgoraRtmPresenceOptions* presence_opt = [[AgoraRtmPresence alloc] init];presence_opt.includeState = false;presence_opt.includeUserId = false;[[rtm getPresence] getOnlineUsers:@"your_channel" channelType:AgoraRtmChannelTypeMessage options:presence_opt completion:^(AgoraRtmgetOnlineUsersResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) { if (errorInfo == nil) { NSLog(@"getOnlineUsers success!!"); int user_count = response.totalOccupancy; } else { NSLog(@"getOnlineUsers failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason); }}];
In this case, only the totalOccupancy
property in the result is valid while all other fields are empty.
You cannot set includeState
to true
and includeUserId
to false
at the same time. To get the temporary status of users, you must also retrieve their user IDs.
Get user channels
The getUserChannels
method enables you to query which channels a user is currently in. This includes the message channels a user has subscribed to and the stream channels they have joined. This method is particularly useful for tracking user paths. Refer to the following sample code:
- Swift
- Objective-C
rtm.getPresence()?.getUserChannels("userId") { response, errorInfo in if errorInfo == nil { print("getUserChannels success!!") let channelCount = response?.totalChannel ?? 0 let channels = response?.channels ?? [] } else { print("getUserChannels failed, errorCode \(errorInfo!.errorCode), reason \(errorInfo!.reason)") }}
[[rtm getPresence] getUserChannels:@"userId" completion:^(AgoraRtmGetUserChannelsResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) { if (errorInfo == nil) { NSLog(@"getUserChannels success!!"); int channel_count = response.totalChannel; NSArray<AgoraRtmChannelInfo *> * channels = response.channels; } else { NSLog(@"getUserChannels failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason); }}];
The getUserChannels
method returns complete query results about the channels and their types that the queried user is in, without pagination.
User status management
Signaling enables you to set and delete user status messages for the local user in each channel. The SDK notifies other online users in the channel of these changes through event notifications. This feature is useful in use-cases where user status sharing is required, such as real-time synchronization of the user's microphone status, mood, personal signature, score, and message input status.
Signaling does not permanently save the status data. When a user unsubscribes from a channel, times out, or exits a channel, the data is deleted. To save user data permanently, refer to Store user metadata.
When a user's temporary status changes, Signaling triggers the remote state changed event notification in real-time. Users who enable presence in features
when subscribing to a channel, receive the event notification.
Set status
Using presence, you can set the temporary user status for the local user. When you set the status before subscribing to or joining a channel, the data is cached on the client and does not take effect immediately. The status is updated and corresponding event notifications are triggered when you subscribe to or join a channel. The setState
method applies to both message and stream channels; use the channelType
parameter to specify the channel type.
- Swift
- Objective-C
let states: [String: String] = ["key1": "value1", "key2": "value2"]rtm.getPresence()?.setState(channelName: "your_channel", channelType: .message, items: states) { response, errorInfo in if errorInfo == nil { print("setState success!!") } else { print("setState failed, errorCode \(errorInfo!.errorCode), reason \(errorInfo!.reason)") }}
NSDictionary *states = @{@"key1": @"value1", @"key2": @"value2"};[[rtm getPresence] setState:@"your_channel" channelType:AgoraRtmChannelTypeMessage items:states completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) { if (errorInfo == nil) { NSLog(@"setState success!!"); } else { NSLog(@"setState failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason); }}];
When using setState
to set temporary user status, if the specified key already exists, its value is overwritten by the new value. If the specified key does not exist, a new key-value pair is added.
Get status
To obtain the temporary user status set by a user in a specified channel, use the getState
method:
- Swift
- Objective-C
rtm.getPresence()?.getState(channelName: "Chat_room", channelType: .message, userId: "userid") { response, errorInfo in if errorInfo == nil { print("getState success!!") if let state = response?.state { // process state } } else { print("getState failed, errorCode \(errorInfo!.errorCode), reason \(errorInfo!.reason)") }}
If the queried user is not present in the specified channel, a presenceUserNotExist
error message is returned by the SDK.
[[rtm getPresence] getState:@"Chat_room" channelType:AgoraRtmChannelTypeMessage userId:@"userid" completion:^(AgoraRtmPresenceGetStateResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) { if (errorInfo == nil) { NSLog(@"getState success!!"); AgoraRtmUserState* state = response.state; } else { NSLog(@"getState failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason); }}];
Use the getState
method to obtain the temporary status of other online users in the channel. If the queried user is not present in the specified channel, a AgoraRtmErrorPresenceUserNotExist
error message is returned by the SDK.
Delete status
Each user can set up to 32 key-value pairs in a channel. To remove items that are no longer needed, call removeState
with a list of keys. The removeState
method only deletes temporary user status data for the local user.
- Swift
- Objective-C
let keys: [String] = ["key1", "key2"]rtm.getPresence()?.removeState("your_channel", channelType: .message, keys: keys) { response, errorInfo in if errorInfo == nil { print("removeState success!!") } else { print("removeState failed, errorCode \(errorInfo!.errorCode), reason \(errorInfo!.reason)") }}
Both the setState
and removeState
methods trigger remoteStateChanged
event notifications. Users who join the channel with presence enabled in features
receive event notifications containing full data of the user's temporary status.
NSArray<NSString*>* keys = @[@"key1", @"key2"];[[rtm getPresence] removeState:@"your_channel" channelType:AgoraRtmChannelTypeMessage keys:keys completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) { if (errorInfo == nil) { NSLog(@"removeState success!!"); } else { NSLog(@"removeState failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason); }}];
Both the setState
and removeState
methods trigger AgoraRtmPresenceEventTypeRemoteStateChanged
event notifications. Users who subscribe to the channel with presence enabled in features
receive event notifications containing full data of the user's temporary status.
Receive presence event notifications
A presence event notification returns the AgoraRtmPresenceEvent data structure, which includes the AgoraRtmPresenceEventType parameter.
To receive presence event notifications, implement an event listener. See event listeners for details. In addition, include presence in the features
parameter when subscribing to or joining a channel.
Event notification modes
The presence event notification mode determines how subscribed users receive event notifications. There are two notification modes:
Announce
: Real-time notification modeInterval
: Scheduled notification mode
Set the Max number of instant event value in Agora Console to specify the condition for switching between the two modes. The scheduled notification mode helps prevent event noise that results from a large number of online users in the channel. See Presence configuration for details.
Real-time notification mode
If the number of instant notifications in the channel is less than the Max number of instant event value, presence event notifications operate in real-time notification mode. In this mode, the following notifications are sent to the client in real-time:
- Swift
- Objective-C
remoteJoinChannel
remoteLeaveChannel
remoteConnectionTimeout
remoteStateChanged
- Join
- Leave
- Timeout
- Snapshot
- State change
{ type: .remoteJoinChannel; channelType: .message; channelName: "test_channel"; publisher: "publisher_name"; states: []; interval: []; snapshot: []; timestamp: 1710487149497;}
{ eventType: .remoteLeaveChannel; channelType: .message; channelName: "test_channel"; publisher: "publisher_name"; stateChanged: []; interval: []; snapshot: []; timestamp: 1710487149497;}
{ eventType: .remoteConnectionTimeout; channelType: .message; channelName: "test_channel"; publisher: "publisher_name"; stateChanged: []; interval: []; snapshot: []; timestamp: 1710487149497;}
{ eventType: .snapshot; channelType: .message; channelName: "test_channel"; publisher: ""; stateChanged: []; interval: []; snapshot: [ { userId: "user_a", states: {}}, { userId: "user_b", states: { key_1: "value_1" }}, { userId: "yourSelf", states: {}}, ]; timestamp: 1710487149497;}
{ eventType: .remoteStateChanged; channelType: .message; channelName: "test_channel"; publisher: "publisher_name"; states: { "key_1": "value_1", }; interval: []; snapshot: []; timestamp: 1710487149497;}
AgoraRtmPresenceEventTypeRemoteJoinChannel
AgoraRtmPresenceEventTypeRemoteLeaveChannel
AgoraRtmPresenceEventTypeRemoteConnectionTimeout
AgoraRtmPresenceEventTypeRemoteStateChanged
- Join
- Leave
- Timeout
- Snapshot
- State change
{ type: AgoraRtmPresenceEventTypeRemoteJoinChannel; channelType: AgoraRtmChannelTypeMessage; channelName: "test_channel"; publisher: "publisher_name"; states: []; interval: []; snapshot: []; timestamp: 1710487149497;}
{ eventType: AgoraRtmPresenceEventTypeRemoteLeaveChannel; channelType: AgoraRtmChannelTypeMessage; channelName: "test_channel"; publisher: "publisher_name"; stateChanged: []; interval: []; snapshot: []; timestamp: 1710487149497;}
{ eventType: AgoraRtmPresenceEventTypeRemoteConnectionTimeout; channelType: AgoraRtmChannelTypeMessage; channelName: "test_channel"; publisher: "publisher_name"; stateChanged: []; interval: []; snapshot: []; timestamp: 1710487149497;}
{ eventType: AgoraRtmPresenceEventTypeSnapshot; channelType: AgoraRtmChannelTypeMessage; channelName: "test_channel"; publisher: ""; stateChanged: []; interval: []; snapshot: [ { userId: "user_a", states: {}}, { userId: "user_b", states: { key_1: "value_1" }}, { userId: "yourSelf", states: {}}, ]; timestamp: 1710487149497;}
{ eventType: AgoraRtmPresenceEventTypeRemoteStateChanged; channelType: AgoraRtmChannelTypeMessage; channelName: "test_channel"; publisher: "publisher_name"; states: { "key_1": "value_1", }; interval: []; snapshot: []; timestamp: 1710487149497;}
Scheduled notification mode
- Swift
- Objective-C
When the number of online users in a channel exceeds the Max number of instant event value, the channel switches to the scheduled notification mode. In this mode, remoteJoinChannel
, remoteLeaveChannel
, remoteConnectionTimeout
, and remoteStateChanged
events are replaced by interval
events and sent to all users in the channel at specific time intervals. Users receive the following notification:
{ "type": "AgoraRtmPresenceEventTypeInterval", "channelType": "AgoraRtmChannelTypeMessages", "channelName": "Chat_room", "publisher": "Tony", "interval": { "remote_join": ["Tony", "Lily"], "remote_leave": ["Jason"], "remote_timeout": ["Wendy"], "remote_state_change": [ { "userId": "Harvard", "states": [ { "Mic": "False" }, { "Position": "Washington" } ] }, { "userId": "Harvard", "states": [ { "Mic": "True" }, { "Position": "Washington" } ] } ] }}
When the number of online users in the channel exceeds the Max number of instant event value, the channel switches to the scheduled notification mode. In this mode, AgoraRtmPresenceEventTypeRemoteJoinChannel
, AgoraRtmPresenceEventTypeRemoteLeaveChannel
, AgoraRtmPresenceEventTypeRemoteConnectionTimeout
, and AgoraRtmPresenceEventTypeRemoteStateChanged
events are replaced by AgoraRtmPresenceEventTypeInterval
events and sent to all users in the channel at specific time intervals. Users receive the following notification:
{ "type": "AgoraRtmPresenceEventTypeInterval", "channelType": "AgoraRtmChannelTypeMessages", "channelName": "Chat_room", "publisher": "Tony", "interval": { "remote_join": ["Tony", "Lily"], "remote_leave": ["Jason"], "remote_timeout": ["Wendy"], "remote_state_change": [ { "userId": "Harvard", "states": [ { "Mic": "False" }, { "Position": "Washington" } ] }, { "userId": "Harvard", "states": [ { "Mic": "True" }, { "Position": "Washington" } ] } ] }}
Reference
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.