Store user metadata
Updated
Use metadata to enhance user features in Signaling clients.
The Signaling storage service enables you to store and share contextual user data in your app, such as name, date-of-birth, avatar, and connections. When user metadata is set, updated, or deleted, the SDK triggers a storage event notification. Other users in the channel receive this notification within 100ms and use the information according to your business logic.
Understand the tech
Use metadata enables you to store and share user level information. A set of user metadata facilitates business-level data storage and real-time notifications. Each user has only one set of user metadata, but each set may contain multiple metadata items. For relevant restrictions, refer to the API usage restrictions. Each metadata item has key, value, and revision properties.
User metadata is stored permanently in the Signaling database. The data persists even after a user logs out. You must explicitly delete it to remove it from the database. This feature impacts your storage billing. Refer to Pricing for details.
Prerequisites
Ensure that you have:
- Integrated the Signaling SDK in your project .
- Implemented the framework functionality from the SDK quickstart page.
- Enabled storage in Storage configuration.
Implement user metadata storage
The section shows you to implement user metadata storage in your Signaling app.
Set user metadata
To create a new metadata item for the user, or to update the value of am existing item, call setUserMetadata. This method creates a new item in the user metadata if the specified key does not exist, or overwrites the associated value if a metadata item with the specified key already exists.
The following example saves a set of metadata items for a specified user. It configures the options parameter to add timestamp and modifier information to each metadata item.
let metadata = AgoraRtmMetadata()!
let name = AgoraRtmMetadataItem()
name.key = "Name"
name.value = "Tony"
let age = AgoraRtmMetadataItem()
age.key = "Age"
age.value = "40"
let avatar = AgoraRtmMetadataItem()
avatar.key = "Avatar"
avatar.value = "https://your-domain/avatar/tong.png"
let itemArray = [name, age, avatar]
metadata.items = itemArray
let metadataOpt = AgoraRtmMetadataOptions()
metadataOpt.recordUserId = true
metadataOpt.recordTs = true
rtm.getStorage()?.setUserMetadata(userId: "Tony", data: metadata, options: metadataOpt) { response, errorInfo in
if errorInfo == nil {
print("setUserMetadata success!!")
} else {
if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
print("setUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
}
}
}When the call is successful, the SDK returns the AgoraRtmCommonResponse data structure. Additionally, Signaling triggers a didReceiveStorageEvent notification of event type update within 100 ms to inform other channel members.
AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];
AgoraRtmMetadataItem* Name = [[AgoraRtmMetadataItem alloc] init];
Name.key = @"Name";
Name.value = @"Tony";
AgoraRtmMetadataItem* Age = [[AgoraRtmMetadataItem alloc] init];
Age.key = @"Age";
Age.value = @"40";
AgoraRtmMetadataItem* Avatar = [[AgoraRtmMetadataItem alloc] init];
Avatar.key = @"Avatar";
Avatar.value = @"https://your-domain/avatar/tony.png";
NSArray *item_array = [NSArray arrayWithObjects:Name, Age, Avatar];
metadata.items = item_array;
AgoraRtmMetadataOptions* metadata_opt = [[AgoraRtmMetadataOptions alloc] init];
metadata_opt.recordUserId = true;
metadata_opt.recordTs = true;
[[rtm getStorage] setUserMetadata:@"Tony" data:metadata options:metadata_opt completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
NSLog(@"setUserMetadata success!!");
} else {
NSLog(@"setUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
}
}];When the call is successful, the SDK returns the AgoraRtmCommonResponse data structure. Additionally, Signaling triggers a didReceiveStorageEvent notification of event type AgoraRtmStorageEventTypeUpdate within 100 ms to inform other channel members.
Get user metadata
To retrieve all metadata items associated with a specific user, call getUserMetadata. Refer to the following example:
// Retrieve user metadata
rtm.getStorage()?.getUserMetadata(userId: "Tony") { response, errorInfo in
if errorInfo == nil {
print("getUserMetadata success!!")
if let data = response?.data {
print("Retrieved metadata: \\(data)")
}
} else {
if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
print("getUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
}
}
}[[rtm getStorage] getUserMetadata:@"Tony" completion:^(AgoraRtmGetMetadataResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
NSLog(@"getUserMetadata success!!");
AgoraRtmMetadata* data = response.data; //get storage data;
} else {
NSLog(@"getUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
}
}];You can also leave the userId parameter blank to get the local user's metadata:
rtm.getStorage()?.getUserMetadata(userId: "") { response, errorInfo in
if errorInfo == nil {
print("getUserMetadata success!!")
if let data = response?.data {
}
} else {
if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
print("getUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
}
}
}[[rtm getStorage] getUserMetadata:@"" completion:^(AgoraRtmGetMetadataResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
NSLog(@"getUserMetadata success!!");
AgoraRtmMetadata* data = response.data; //get storage data;
} else {
NSLog(@"getUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
}
}];Signaling returns the following data structure:
{
majorRevision: 734874892,
metadata:{
{
key:"Name",
value:"Tony",
revision:734874872,
updateTs:1688978391900,
authorUid:"Tony"
},
{
key:"Age",
value:"40",
revision:734874862,
updated:1688978390900,
authorUid:"Tony"
},
{
key:"Avatar",
value:"https://your-domain/avatar/tony.png",
revision:734874812,
updated:1688978382900,
authorUid:"Tony"
}
}
}Update user metadata
To modify existing metadata items, call updateUserMetadata. If the metadata item does not exist, the SDK returns an error. This method is useful for business use-cases that require permission control on creating new metadata items. For example, the admin defines the user metadata fields and users may only update the values.
The following example updates the value of an existing metadata item:
let metadata = AgoraRtmMetadata()!
let age = AgoraRtmMetadataItem()
age.key = "Age"
age.value = "45"
let itemArray = [age]
metadata.items = itemArray
let metadataOpt = AgoraRtmMetadataOptions()
metadataOpt.recordUserId = true
metadataOpt.recordTs = true
rtm.getStorage()?.updateUserMetadata(userId: "Tony", data: metadata, options: metadataOpt) { response, errorInfo in
if errorInfo == nil {
print("updateUserMetadata success!!")
} else {
if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
print("updateUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
}
}
}When the call is successful, the SDK returns the AgoraRtmCommonResponse data structure. Additionally, Signaling triggers a didReceiveStorageEvent notification of event type update within 100 ms to inform other channel members.
AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];
AgoraRtmMetadataItem* Age = [[AgoraRtmMetadataItem alloc] init];
Age.key = @"Age";
Age.value = @"45";
NSArray *item_array = [NSArray arrayWithObjects:Age];
metadata.items = item_array;
AgoraRtmMetadataOptions* metadata_opt = [[AgoraRtmMetadataOptions alloc] init];
metadata_opt.recordUserId = true;
metadata_opt.recordTs = true;
[[rtm getStorage] updateUserMetadata:@"Tony" data:metadata options:metadata_opt completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
NSLog(@"updateUserMetadata success!!");
} else {
NSLog(@"updateUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
}
}];When the call is successful, the SDK returns the AgoraRtmCommonResponse data structure. Additionally, Signaling triggers a didReceiveStorageEvent notification of event type AgoraRtmStorageEventTypeUpdate within 100 ms to inform other channel members.
Delete user metadata
To delete metadata items that are no longer required, call removeUserMetadata. Refer to the following sample code:
let metadata = AgoraRtmMetadata()!
let age = AgoraRtmMetadataItem()
age.key = "Age"
let itemArray = [age]
metadata.items = itemArray
rtm.getStorage()?.removeUserMetadata(userId: "Tony", data: metadata, options: nil) { response, errorInfo in
if errorInfo == nil {
print("removeUserMetadata success!!")
} else {
if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
print("removeUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
}
}
}Setting the value for a metadata item that is being deleted has no effect.
When the call is successful, the SDK returns the AgoraRtmCommonResponse data structure. Additionally, Signaling triggers a didReceiveStorageEvent notification of event type update within 100 ms to inform other channel members.
AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];
AgoraRtmMetadataItem* Age = [[AgoraRtmMetadataItem alloc] init];
Age.key = @"Age";
NSArray *item_array = [NSArray arrayWithObjects:Age];
metadata.items = item_array;
[[rtm getStorage] removeUserMetadata:@"Tony" data:metadata options:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
NSLog(@"removeUserMetadata success!!");
} else {
NSLog(@"removeUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
}
}];Setting the value for a metadata item that is being deleted has no effect.
When the call is successful, the SDK returns the AgoraRtmCommonResponse data structure. Additionally, Signaling triggers a didReceiveStorageEvent notification of event type AgoraRtmStorageEventTypeUpdate within 100 ms to inform other channel members.
To delete the entire set of metadata for a user, do not add any metadata items when calling removeUserMetadata. Refer to the following sample code:
let metadata = AgoraRtmMetadata()!
// Remove user metadata
rtm.getStorage()?.removeUserMetadata(userId: "Tony", data: metadata, options: nil) { response, errorInfo in
if errorInfo == nil {
print("removeUserMetadata success!!")
} else {
if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
print("removeUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
}
}
}AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];
[[rtm getStorage] removeUserMetadata:@"Tony" data:metadata options:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
NSLog(@"removeUserMetadata success!!");
} else {
NSLog(@"removeUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
}
}];When terminating a user account, it is common to delete the entire set of user's metadata. Once user metadata is deleted, it cannot be recovered. To implement data restoration, back up the metadata before deleting it.
Receive storage event notifications
A storage event notification returns the AgoraRtmStorageEvent data structure, which includes the AgoraRtmStorageEventType parameter.
To receive storage event notifications, implement an event listener. See event listeners for details. You only receive user metadata update notifications for users that you have subscribed to.
Event notification mode
Currently, Signaling only supports full data update mode. This means that when a user's metadata is updated, the data field in the event notification contains all the metadata of the user.
Subscribe to a user's metadata
To monitor updates to a user's metadata, you subscribe to their metadata. Refer to the following sample code:
rtm.getStorage()?.subscribeUserMetadata(userId: "Tony") { response, errorInfo in
if errorInfo == nil {
print("subscribeUserMetadata success!!")
} else {
if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
print("subscribeUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
}
}
}When there are changes in the user metadata, Signaling triggers a didReceiveStorageEvent notification of event type update within 100 ms to inform all users who have subscribed to this user's metadata.
[[rtm getStorage] subscribeUserMetadata:@"Tony" completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
NSLog(@"subscribeUserMetadata success!!");
} else {
NSLog(@"subscribeUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
}
}];When there are changes in the user metadata, Signaling triggers a didReceiveStorageEvent notification of event type AgoraRtmStorageEventTypeUpdate within 100 ms to inform all users who have subscribed to this user's metadata.
Unsubscribe from a user's metadata
When you no longer need to receive notifications about a user's metadata updates, unsubscribe from the users's metadata. Refer to the following sample code:
rtm.getStorage()?.unsubscribeUserMetadata(userId: "Tony") { response, errorInfo in
if errorInfo == nil {
print("unsubscribeUserMetadata success!!")
} else {
if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
print("unsubscribeUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
}
}
}[[rtm getStorage] unsubscribeUserMetadata:@"Tony" completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
NSLog(@"unsubscribeUserMetadata success!!");
} else {
NSLog(@"unsubscribeUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
}
}];Version control
Signaling integrates compare-and-set (CAS) version control to manage metadata updates. CAS is a concurrency control mechanism to ensure that updates to a shared resource occur only if the resource is in an expected state. The mechanism works as follows:
- The client reads the current version of a data item.
- Before making an update, the client compares the current version with the last read version number.
- If the versions match, the client proceeds with the update and increments the version number. If they do not match, the update is aborted.
CAS version control is useful in use-cases that require concurrency management. For instance, consider a dating application where only one user may engage in a chat session with a host. When multiple users attempt to join, only the first request is successful.
The CAS version control feature provides two independent version control parameters. Set one or more of these values according to the needs of your business use-case:
-
majorRevisionparameter in thesetMajorRevisionmethod: Enable version number verification of the entire set of user metadata. -
revisionparameter of aMetadataItem: Enable version number verification of a single metadata item.
When setting user metadata, or a single user metadata item, use the revision attribute to enable or disable version control as follows:
-
To disable CAS verification, use the default value of
-1for therevisionparameter. -
To enable CAS verification, set the
majorRevisionor therevisionparameter to a positive integer. The SDK updates the corresponding value after successfully verifying the revision number. If the specified revision number does not match the latest revision number in the database, the SDK returns an error.
The following code snippet demonstrates how to employ majorRevision and revision for updating user metadata and metadata items:
let metadata = AgoraRtmMetadata()!
let sessionRequest = AgoraRtmMetadataItem()
// Set user avatar URL as metadata
sessionRequest.key = "Avatar"
sessionRequest.value = "https://your-domain/avatar/tony.png"
sessionRequest.revision = 734874812
// Create an array of metadata items
let itemArray = [sessionRequest]
metadata.items = itemArray
metadata.majorRevision = 734874892 // Set major revision
let metadataOptions = AgoraRtmMetadataOptions()
// Enable recording of user ID and timestamp
metadataOptions.recordUserId = true
metadataOptions.recordTs = true
// Update user metadata
rtm.getStorage().updateUserMetadata(userId: "Tony", data: metadata, options: metadataOptions) { response, errorInfo in
if errorInfo == nil {
print("updateUserMetadata success!!")
} else {
if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
print("updateUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
}
}
}AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];
AgoraRtmMetadataItem* sessionRequest = [[AgoraRtmMetadataItem alloc] init];
sessionRequest.key = @"Avatar";
sessionRequest.value = @"https://your-domain/avatar/tony.png";
sessionRequest.revision = 734874812;
NSArray *item_array = [NSArray arrayWithObjects:sessionRequest];
metadata.items = item_array;
metadata.majorRevision = 734874892; //set major revision
AgoraRtmMetadataOptions* metadata_opt = [[AgoraRtmMetadataOptions alloc] init];
metadata_opt.recordUserId = true;
metadata_opt.recordTs = true;
[[rtm getStorage] updateUserMetadata:@"Tony" data:metadata options:metadata_opt completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
NSLog(@"updateUserMetadata success!!");
} else {
NSLog(@"updateUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
}
}];In this example, CAS verification for user metadata and metadata items is enabled by setting majorRevision and revision parameters to positive integers. Upon receiving the update call request, Signaling first verifies the provided major revision number against the latest value in the database. If there's a mismatch, it returns an error; if the values match, Signaling verifies the revision number for each metadata item using a similar logic.
When using version control, monitor didReceiveStorageEvent notifications to retrieve updated values for majorRevision and revision to ensure that the latest revision values are used for subsequent operations.
Reference
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
