Skip to main content

You are viewing Agora Docs forBeta products and features. Switch to Docs

Store channel and user data

Signaling storage is a powerful feature that enables you to store and manage channel and user attributes in a serverless storage system. It acts as an extension of your database, offering synchronization across all participants. By using signaling storage, you can develop innovative, reliable, and scalable applications without the hassle of setting up your own database. This seamless integration ensures that data is readily available and up-to-date for all users, making it easier to build real-time and collaborative applications.

Your app receives metadata modification events in real-time enabling you to update the front-end accordingly. You can leverage user and channel metadata in Signaling apps to:

  • Maintain search history: Enable users to quickly search for specific messages, users, or topics within the app.

  • Customize notifications: Enable users to customize their notification settings, such as choosing notifications to receive and setting custom alert tones.

  • Enhance security: Enforce security measures, such as verifying user identity and encrypting messages.

Understand the tech

Using Signaling storage you associate metadata with a particular channel or a specific user:

  • Channel metadata: store and distribute contextual channel data in your app, such as props, announcements, member lists, and relationship chains. When channel properties are set, updated, or deleted by a user, an event notification is triggered, and other users in the channel receive this information within 100ms.

  • User metadata: store and distribute contextual data about users in your app. A user has one set of metadata with one or more attributes. When an attribute is set, updated or deleted, an event notification is triggered, and users who subscribe to this users metadata will receive this information within 100ms.

This section presents an overview of the steps required to integrate Signaling storage into your app. The following figure shows the basic workflow you implement to read and write channel and user metadata:

Signaling workflow

Prerequisites

To follow this page, you must have:

Implement storage

The following code examples show how to implement storage features in your Signaling app:

Set local user metadata

To set a key-value pair for the local user's metadata:


_19
fun setUserMetadata(uid: Int, key: String, value: String) {
_19
// Create a metadata instance
_19
val metadata: Metadata = signalingEngine!!.storage!!.createMetadata()
_19
_19
// Add a metadata item
_19
metadata.setMetadataItem(MetadataItem(key, value, -1))
_19
metadata.majorRevision = -1
_19
_19
signalingEngine?.storage?.setUserMetadata(uid.toString(), metadata,
_19
MetadataOptions(true, true), object: ResultCallback<Void?> {
_19
override fun onSuccess(responseInfo: Void?) {
_19
notify("User metadata set successfully")
_19
}
_19
_19
override fun onFailure(errorInfo: ErrorInfo) {
_19
notify("Failed to set user metadata: $errorInfo")
_19
}
_19
})
_19
}

Update local user metadata

To update a value in the local user's metadata:


_18
fun updateUserMetadata(uid: Int, key: String, value: String) {
_18
// Create a metadata instance
_18
val metadata: Metadata = signalingEngine!!.storage!!.createMetadata()
_18
_18
// Add a metadata item
_18
metadata.setMetadataItem(MetadataItem(key, value, -1))
_18
_18
signalingEngine?.storage?.updateUserMetadata(uid.toString(), metadata,
_18
MetadataOptions(true, true), object: ResultCallback<Void?> {
_18
override fun onSuccess(responseInfo: Void?) {
_18
notify("User metadata updated successfully")
_18
}
_18
_18
override fun onFailure(errorInfo: ErrorInfo) {
_18
notify("Failed to update user metadata: $errorInfo")
_18
}
_18
})
_18
}

Retrieve a user's metadata

To get all the key-value pairs stored in a user's metadata:


_18
fun getUserMetadata(uid: Int) {
_18
signalingEngine?.storage?.getUserMetadata(uid.toString(), object: ResultCallback<Metadata?> {
_18
override fun onSuccess(data: Metadata?) {
_18
val items = data?.metadataItems
_18
var summary = "User metadata\n"
_18
if (items != null) {
_18
for (item in items) {
_18
summary += "${item.key}: ${item.value}\n"
_18
}
_18
}
_18
notify(summary)
_18
}
_18
_18
override fun onFailure(errorInfo: ErrorInfo) {
_18
notify("Failed to get user metadata: $errorInfo")
_18
}
_18
})
_18
}

Subscribe to a user's metadata

To receive notifications of modifications to a user's data, you subscribe to the users metadata:


_17
fun subscribeUserMetadata(uid: Int) {
_17
if (subscribedIds.contains(uid)) {
_17
return
_17
}
_17
signalingEngine?.storage?.subscribeUserMetadata(
_17
uid.toString(),
_17
object : ResultCallback<Void?> {
_17
override fun onSuccess(responseInfo: Void?) {
_17
notify("Subscribe user metadata success")
_17
subscribedIds.add(uid)
_17
}
_17
_17
override fun onFailure(errorInfo: ErrorInfo) {
_17
notify(errorInfo.toString())
_17
}
_17
})
_17
}

Set channel metadata

To store a key-value pair in the channel's metadata:


_19
fun setChannelMetadata(key: String, value: String, revision: Long, lockName: String) {
_19
// Create a metadata instance
_19
val metadata: Metadata = signalingEngine!!.storage!!.createMetadata()
_19
_19
// Add a metadata item
_19
metadata.setMetadataItem(MetadataItem(key, value, revision))
_19
metadata.majorRevision = -1
_19
_19
signalingEngine?.storage?.setChannelMetadata(channelName, channelType, metadata,
_19
MetadataOptions(true, true), lockName, object: ResultCallback<Void?> {
_19
override fun onSuccess(responseInfo: Void?) {
_19
notify("Channel metadata set successfully")
_19
}
_19
_19
override fun onFailure(errorInfo: ErrorInfo) {
_19
notify("Failed to set channel metadata: $errorInfo")
_19
}
_19
})
_19
}

Retrieve channel metadata

To get all the key-value pairs stored in a channels's metadata:


_18
fun getChannelMetadata() {
_18
signalingEngine?.storage?.getChannelMetadata(channelName, channelType, object: ResultCallback<Metadata?> {
_18
override fun onSuccess(data: Metadata?) {
_18
var summary = "Channel metadata\n"
_18
val items = data?.metadataItems
_18
if (items != null) {
_18
for (item in items) {
_18
summary += "${item.key}: ${item.value}\n"
_18
}
_18
}
_18
notify(summary)
_18
}
_18
_18
override fun onFailure(errorInfo: ErrorInfo) {
_18
notify(errorInfo.toString())
_18
}
_18
})
_18
}

Receive notification when a user's metadata is updated

To get notified of changes in a user's metadata, handle the event callback:


_32
override fun onStorageEvent(storageEventArgs: StorageEvent) {
_32
// Your Storage Event handler
_32
when (storageEventArgs.storageType) {
_32
RtmConstants.RtmStorageType.CHANNEL -> {
_32
// channel metadata was updated
_32
showChannelMetadata(storageEventArgs.data)
_32
}
_32
RtmConstants.RtmStorageType.USER -> {
_32
// user metadata was updated
_32
showMessage(
_32
"Metadata event " +
_32
storageEventArgs.eventType +
_32
", User: " +
_32
storageEventArgs.target
_32
)
_32
//showUserMetadata(eventArgs.publisher, eventArgs.data.metadata);
_32
}
_32
else -> {
_32
showMessage("Storage event: ${storageEventArgs.eventType}")
_32
}
_32
}
_32
}
_32
_32
override fun onTopicEvent(eventArgs: TopicEvent) {
_32
// Your Topic Event handler
_32
_32
}
_32
_32
override fun onLockEvent(eventArgs: LockEvent) {
_32
// Your Lock Event handler
_32
_32
}

Manage Locks

Locks enable you to safely share critical resources between different users and processes in a distributed system. To set, acquire, release, remove, revoke locks, and get locks information:


_66
fun setLock (lockName: String, ttl: Long) {
_66
// ttl is the lock expiration time in case the user goes offline
_66
signalingEngine?.lock?.setLock(channelName, channelType, lockName, ttl, object: ResultCallback<Void?> {
_66
override fun onSuccess(responseInfo: Void?) {
_66
notify("Lock set successfully")
_66
}
_66
_66
override fun onFailure(errorInfo: ErrorInfo) {
_66
notify(errorInfo.toString())
_66
}
_66
})
_66
}
_66
_66
fun acquireLock(lockName: String, retry: Boolean) {
_66
signalingEngine?.lock?.acquireLock(channelName, channelType, lockName, retry, object: ResultCallback<Void?> {
_66
override fun onSuccess(responseInfo: Void?) {
_66
notify("Lock acquired successfully")
_66
}
_66
_66
override fun onFailure(errorInfo: ErrorInfo) {
_66
notify(errorInfo.toString())
_66
}
_66
})
_66
}
_66
_66
fun releaseLock(lockName: String, retry: Boolean) {
_66
signalingEngine?.lock?.releaseLock(channelName, channelType, lockName, object: ResultCallback<Void?> {
_66
override fun onSuccess(responseInfo: Void?) {
_66
notify("Lock released successfully")
_66
}
_66
_66
override fun onFailure(errorInfo: ErrorInfo) {
_66
notify(errorInfo.toString())
_66
}
_66
})
_66
}
_66
_66
fun removeLock(lockName: String) {
_66
signalingEngine?.lock?.releaseLock(channelName, channelType, lockName, object: ResultCallback<Void?> {
_66
override fun onSuccess(responseInfo: Void?) {
_66
notify("Lock released successfully")
_66
}
_66
_66
override fun onFailure(errorInfo: ErrorInfo) {
_66
notify(errorInfo.toString())
_66
}
_66
})
_66
}
_66
_66
fun getLocks() {
_66
signalingEngine?.lock?.getLocks(channelName, channelType, object: ResultCallback<ArrayList<LockDetail?>> {
_66
override fun onSuccess(lockDetail: ArrayList<LockDetail?>) {
_66
var summary = "Lock details:\n"
_66
for (lock in lockDetail) {
_66
if (lock != null) {
_66
summary += "Lock: ${lock.lockName}, Owner:${lock.lockOwner}"
_66
}
_66
}
_66
notify(summary)
_66
}
_66
_66
override fun onFailure(errorInfo: ErrorInfo) {
_66
notify(errorInfo.toString())
_66
}
_66
})
_66
}

Test storage

This section explains how to run the Store channel and user data example in the reference app and test channel and user metadata features.

The example implements the following functionality. After a local user logs in to Signaling, they set their metadata in the form of key-value pairs. When the user modifies a value stored as metadata, they call the update method to save the new value. The app show a list of all users currently in the channel. When the local user selects another user from the list, the remote users metadata is retrieved and displayed. The local user subscribes to the metadata of remote users to be notified of any changes. The local user can set channel metadata using revision and lock features.

To test this functionality:

  1. Configure the project

    1. Open the file <samples-root>/signaling-manager/src/main/res/raw/config.json

    2. Set appId to the AppID of your project.

    3. Choose one of the following authentication methods:

      • Temporary token:
        1. Generate an RTM token using your uid.
        2. Set token to this value in config.json.
      • Authentication server:
        1. Setup an Authentication server
        2. In config.json, set:
          • token to an empty string.
          • serverUrl to the base URL for your token server. For example: https://agora-token-service-production-yay.up.railway.app.
  2. Run the reference app

    1. In Android Studio, connect a physical Android device to your development machine.
    2. Click Run to start the app.
    3. A moment later you see the project installed on your device.
  3. Login to Signaling as a first user

    1. Enter a numeric User ID and press Login to log in to Signaling.

    2. Enter a Channel name and press Subscribe to subscribe to a channel. You see the following:

      1. Your User ID appears in the users list.
      2. You see all the key-value pairs previously saved to the channel metadata.
      3. You see notifications confirming update of user metadata.
  4. Login to Signaling as another user

    1. Launch another reference app instance, and log in using a different User ID. Enter the same channel name as before, and press Subscribe.

      You see that both User IDs appear in the user lists in both instances.

  5. Test channel metadata

    In either instance, type in a Key and the Value you wish to store in the channel metadata, then press Update. You see that the key-value pair is added or updated in the Channel Metadata in both instances.

  6. Test version control

    1. Under Set channel metadata, type a new Value for an existing channel metadata Key. Enter a random positive integer for Revision and press Update. The log events section shows that the storage operation fails due to outdated revision.

    2. Update the Revision value to show the same number as displayed against the metadata key under Channel Metadata, then press Update again. This time, the operation succeeds.

  7. Test locks

    1. Under Manage Locks, type a Lock Name and press Set. You see a SET event notification confirming creation of the lock.

    2. In Lock to apply, enter the same lock name and press Update. You see an error reporting that the lock has not been acquired.

    3. Press Acquire. You see an ACQUIRED event notification confirming acquisition of the lock.

    4. Press Update, this time the channel key value is updated under Channel Metadata.

    5. Press Get. You see details of all current locks in the logs section.

    6. Press Release to release the lock, and Remove to delete it.

  8. Test user metadata

    1. In the user bio box, type in some text and press Update Bio. You see user metadata UPDATE notifications in both instances.

    2. In the other instance, select the user for which you modified the User bio. You see their updated metadata bio.

Reference

This section contains additional information that either supplements the content on this page or directs you to documentation that covers other aspects of this product.

Signaling