For AI agents: see the complete documentation index at /llms.txt.
Signaling Quickstart
Updated
Rapidly develop your first Signaling app.
Use Signaling SDK to add low-latency, high-concurrency signaling and synchronization capabilities to your app.
Signaling also helps you enhance the user experience in Video Calling, Voice Calling, Interactive Live Streaming, and Broadcast Streaming applications.
This page shows you how to use the Signaling SDK to rapidly build a simple application that sends and receives messages. It shows you how to integrate the Signaling SDK in your project and implement pub/sub messaging through Message channels. To get started with stream channels, follow this guide to create a basic Signaling app and then refer to the Stream channels guide.
Understand the tech
To use Signaling features in your app, you initialize a Signaling client instance and add event listeners. To connect to Signaling, you login using an authentication token. To send a message to a message channel, you publish the message. Signaling creates a channel when a user subscribes to it. To receive messages other users publish to a channel, your app listens for events.
To create a pub/sub session for Signaling, implement the following steps in your app:
Prerequisites
To implement the code presented on this page you need to have:
-
Enabled Signaling in Agora Console
-
Android Studio 4.1 or higher.
-
Android SDK API Level 24 or higher.
-
A mobile device that runs Android 4.1 or higher.
-
Ensure that a firewall is not blocking your network communication.
Signaling 2.x is an enhanced version compared to 1.x with a wide range of new features. It follows a new pricing structure. See Pricing for details.
Project setup
Create a project
-
Create a new project.
-
Open Android Studio and select File > New > New Project....
-
Select Phone and Tablet > Empty Activity and click Next.
-
Set the project name and storage path.
-
Select Java or Kotlin as the language, and click Finish to create the project.
After you create a project, Android Studio automatically starts gradle sync. Ensure that the synchronization is successful before proceeding to the next step.
-
-
Add network permissions
Open the
/app/src/main/AndroidManifest.xmlfile and add the following permissions before<application>:<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -
Prevent code obfuscation
Open the
/app/proguard-rules.profile and add the following line to prevent code obfuscation:-keep class io.agora.**{*;}
Integrate the SDK
Use either of the following methods to integrate Signaling SDK into your project.
Using Maven Central
-
Open the
settings.gradlefile in the project's root directory and add the Maven Central dependency, if it doesn't already exist:repositories { mavenCentral() }If your Android project uses
dependencyResolutionManagement, there may be differences in how you add Maven Central dependencies. -
Add the following to the
/Gradle Scripts/build.gradle(Module: <projectname>.app)file underdependenciesto integrate the SDK into your Android project:-
Groovy
implementation 'io.agora.rtm:rtm-sdk:x.y.z'To resolve integration issues when co-integrating with the RTC SDK use:
implementation 'io.agora.rtm:rtm-sdk-lite:x.y.z' -
Kotlin
implementation("io.agora.rtm:rtm-sdk:x.y.z")To resolve integration issues when co-integrating with the RTC SDK use:
implementation("io.agora.rtm:rtm-sdk-lite:x.y.z")
Replace
x.y.zwith the specific SDK version number, such as2.2.8. To get the latest version number, check the Release notes. -
Using CDN
-
Download the latest version of Signaling SDK for Android.
-
Copy all files in the
sdkfolder of the package to the/app/libsfolder of the project. -
To add the SDK reference, open the project file
/Gradle Scripts/build.gradle(Module: <projectname>.app)and add the following code:-
Add a
ndknode under the defaultConfignode, to specify the supported architectures:Config { // ... ndk{ abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' } }Supporting all architectures increases the app size. Best practice is to only add essential architectures based on your targets. For most use-cases,
armeabi-v7aandarm64-v8aarchitectures are sufficient when releasing the Android app. -
Add a
sourceSetsnode under theandroidnode to include the jni libraries copied to thelibsfolder:android { // ... sourceSets { main { jniLibs.srcDirs = ['libs'] } } } -
To include all
jarfiles in thelibsfolder as dependencies, add the following under thedependenciesnode:dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // ... }
-
To integrate Signaling SDK version 2.2.0 or later alongside Video SDK version 4.3.0 or later, refer to handle integration issues.
Create a user interface
This section helps you create a simple user interface to explore the basic features of Signaling. Modify it according to your specific needs.
The demo interface consists of the following UI elements:
- Input boxes for user ID, channel name, and message
- Buttons to log in and log out of Signaling
- Buttons to subscribe and unsubscribe from a channel
- A button to publish a message
Sample code to create the user interface
Open the /app/res/layout/activity_main.xml file, and replace the contents with the following:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/uid"
android:layout_width="130dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="40dp"
android:hint="@string/uid"
android:inputType="text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/logout_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:onClick="onClickLogout"
android:text="@string/logout_button"
app:layout_constraintStart_toEndOf="@+id/login_button"
app:layout_constraintBottom_toBottomOf="@id/uid" />
<Button
android:id="@+id/login_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:onClick="onClickLogin"
android:text="@string/login_button"
app:layout_constraintStart_toEndOf="@+id/uid"
app:layout_constraintBottom_toBottomOf="@id/uid" />
<EditText
android:id="@+id/channel_name"
android:layout_width="130dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:hint="@string/channel_name"
android:inputType="text"
app:layout_constraintTop_toBottomOf="@+id/uid"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/subscribe_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:onClick="onClickSubscribe"
android:text="@string/subscribe_button"
app:layout_constraintStart_toEndOf="@+id/channel_name"
app:layout_constraintBottom_toBottomOf="@+id/channel_name" />
<Button
android:id="@+id/unsubscribe_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:onClick="onClickUnsubscribe"
android:text="@string/unsubscribe_button"
app:layout_constraintStart_toEndOf="@+id/subscribe_button"
app:layout_constraintBottom_toBottomOf="@+id/subscribe_button" />
<EditText
android:id="@+id/msg_box"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:hint="@string/msg"
android:inputType="text"
android:singleLine="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/channel_name" />
<Button
android:id="@+id/send_channel_msg_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:onClick="onClickSendChannelMsg"
android:text="@string/send_channel_msg_button"
app:layout_constraintTop_toBottomOf="@+id/msg_box"
app:layout_constraintEnd_toEndOf="@id/msg_box" />
<TextView
android:id="@+id/message_history"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="20dp"
android:background="#eee"
android:paddingStart="10dp"
android:paddingEnd="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/send_channel_msg_button" />
</androidx.constraintlayout.widget.ConstraintLayout>Open the app/res/values/strings.xml file and add following string resources:
<resources>
<string name="app_name">Signaling Quickstart</string>
<string name="login_button">Login</string>
<string name="logout_button">Logout</string>
<string name="subscribe_button">Subscribe</string>
<string name="unsubscribe_button">Unsubscribe</string>
<string name="send_channel_msg_button">Publish message</string>
<string name="uid">User ID</string>
<string name="msg">Message content</string>
<string name="channel_name">Channel name</string>
<string name="app_id">your_appid</string>
<string name="token">your_token</string>
</resources>Implement Signaling
A complete code sample that implements the basic features of Signaling is presented here for your reference. To use the sample code, copy the following lines into the /app/src/main/java/com/example/<projectname>/MainActivity file and replace <projectname> in package com.example.<projectname> with the name of your project.
Complete sample code for Signaling
package com.example.<projectname>;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import io.agora.rtm.*;
public class JavaActivity extends AppCompatActivity {
private EditText etUserId;
private EditText etChannelName;
private EditText etMessageContent;
private TextView mMessageHistory;
private RtmClient mRtmClient;
private final RtmEventListener eventListener = new RtmEventListener() {
@Override
public void onMessageEvent(MessageEvent event) {
String text = "Message received from " + event.getPublisherId()
+ ", Message: " + event.getMessage().getData();
writeToMessageHistory(text);
}
@Override
public void onPresenceEvent(PresenceEvent event) {
String text = "Received presence event, user: " + event.getPublisherId()
+ ", Event: " + event.getEventType();
writeToMessageHistory(text);
}
@Override
public void onLinkStateEvent(LinkStateEvent event) {
String text = "Connection state changed to " + event.getCurrentState()
+ ", Reason: " + event.getReason();
writeToMessageHistory(text);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onClickLogin(View v) {
etUserId = findViewById(R.id.uid);
String userId = etUserId.getText().toString();
String token = getString(R.string.token);
if (createClient(userId)) {
login(token);
}
}
public void onClickLogout(View v) {
logout();
}
public void onClickSubscribe(View v) {
etChannelName = findViewById(R.id.channel_name);
String channelName = etChannelName.getText().toString();
subscribe(channelName);
}
public void onClickUnsubscribe(View v) {
etChannelName = findViewById(R.id.channel_name);
String channelName = etChannelName.getText().toString();
unsubscribe(channelName);
}
public void onClickSendChannelMsg(View v) {
etChannelName = findViewById(R.id.channel_name);
String channelName = etChannelName.getText().toString();
etMessageContent = findViewById(R.id.msg_box);
String message = etMessageContent.getText().toString();
publishMessage(channelName, message);
}
private boolean createClient(String userId) {
if (userId.isEmpty()) {
showToast("Invalid userId");
return false;
}
try {
RtmConfig config = new RtmConfig.Builder(getString(R.string.app_id), userId)
.eventListener(eventListener)
.build();
mRtmClient = RtmClient.create(config);
return true;
} catch (Exception e) {
showToast("Error creating RTM client.");
return false;
}
}
private void login(String token) {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
mRtmClient.login(token, new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully logged in to Signaling!");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to log in to Signaling: " + errorInfo);
}
});
}
private void logout() {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
mRtmClient.logout(new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully logged out.");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to log out: " + errorInfo);
}
});
}
private void subscribe(String channelName) {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
SubscribeOptions options = new SubscribeOptions();
options.setWithMessage(true);
mRtmClient.subscribe(channelName, options, new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully subscribed to the channel!");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to subscribe to the channel: " + errorInfo);
}
});
}
private void unsubscribe(String channelName) {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
mRtmClient.unsubscribe(channelName, new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully unsubscribed from the channel!");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to unsubscribe from the channel: " + errorInfo);
}
});
}
private void publishMessage(String channelName, String message) {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
PublishOptions options = new PublishOptions();
options.setCustomType("");
mRtmClient.publish(channelName, message, options, new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Message sent to channel " + channelName + ": " + message);
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to send message to channel " + channelName + ": " + errorInfo);
}
});
}
private void writeToMessageHistory(String record) {
if (mMessageHistory == null) {
mMessageHistory = findViewById(R.id.message_history);
}
mMessageHistory.append("- " + record + "
");
}
private void showToast(String text) {
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
}package com.example.<projectname>
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.View
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import io.agora.rtm.*
class MainActivity : AppCompatActivity() {
private lateinit var etUserId: EditText
private lateinit var etChannelName: EditText
private lateinit var etMessageContent: EditText
private var mRtmClient: RtmClient? = null
private lateinit var mMessageHistory: TextView
private val eventListener = object : RtmEventListener {
override fun onMessageEvent(event: MessageEvent) {
val text = "Message received from \${event.publisherId}, Message: \${event.message.data}"
writeToMessageHistory(text)
}
override fun onPresenceEvent(event: PresenceEvent) {
val text = "Received presence event, user: \${event.publisherId}, Event: \${event.eventType}"
writeToMessageHistory(text)
}
override fun onLinkStateEvent(event: LinkStateEvent) {
val text = "Connection state changed to \${event.currentState}, Reason: \${event.reason}"
writeToMessageHistory(text)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
}
fun onClickLogin(v: View) {
etUserId = findViewById(R.id.uid)
val userId = etUserId.text.toString()
val token = getString(R.string.token)
if (createClient(userId)) {
login(token)
}
}
fun onClickLogout(v: View) {
logout()
}
fun onClickSubscribe(v: View) {
etChannelName = findViewById(R.id.channel_name)
val channelName = etChannelName.text.toString()
subscribe(channelName)
}
fun onClickUnsubscribe(v: View) {
etChannelName = findViewById(R.id.channel_name)
val channelName = etChannelName.text.toString()
unsubscribe(channelName)
}
fun onClickSendChannelMsg(v: View) {
etChannelName = findViewById(R.id.channel_name)
val channelName = etChannelName.text.toString()
etMessageContent = findViewById(R.id.msg_box)
val message = etMessageContent.text.toString()
publishMessage(channelName, message)
}
private fun createClient(userId: String): Boolean {
if (userId.isEmpty()) {
showToast("Invalid userId")
return false
}
return try {
val config = RtmConfig.Builder(getString(R.string.app_id), userId)
.eventListener(eventListener)
.build()
mRtmClient = RtmClient.create(config)
true
} catch (e: Exception) {
showToast("Error creating RTM client.")
false
}
}
private fun login(token: String) {
mRtmClient?.login(token, object : ResultCallback<Void?> {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Successfully logged in to Signaling!")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to log in to Signaling: $errorInfo")
}
}) ?: showToast("RTM client is null")
}
private fun logout() {
mRtmClient?.logout(object : ResultCallback<Void?> {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Successfully logged out.")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to log out: $errorInfo")
}
}) ?: showToast("RTM client is null")
}
private fun subscribe(channelName: String) {
mRtmClient ?: run {
showToast("RTM client is null")
return
}
val options = SubscribeOptions().apply {
withMessage = true
}
mRtmClient!!.subscribe(channelName, options, object : ResultCallback<Void?> {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Successfully subscribed to the channel!")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to subscribe to the channel: $errorInfo")
}
})
}
private fun unsubscribe(channelName: String) {
mRtmClient ?: run {
showToast("RTM client is null")
return
}
mRtmClient!!.unsubscribe(channelName, object : ResultCallback<Void?> {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Successfully unsubscribed from the channel!")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to unsubscribe from the channel: $errorInfo")
}
})
}
private fun publishMessage(channelName: String, message: String) {
mRtmClient ?: run {
showToast("RTM client is null")
return
}
val options = PublishOptions().apply {
customType = ""
}
mRtmClient!!.publish(channelName, message, options, object : ResultCallback<Void?> {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Message sent to channel $channelName: $message")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to send message to channel $channelName: $errorInfo")
}
})
}
private fun writeToMessageHistory(record: String) {
mMessageHistory = findViewById(R.id.message_history)
mMessageHistory.append("- $record
")
}
private fun showToast(text: String) {
Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
}
}Follow the implementation steps to understand the core API calls in the sample code or use the snippets in your own code.
Import Agora classes
To use Signaling APIs in your project, import the relevant Agora classes and interfaces:
import io.agora.rtm.*;import io.agora.rtm.*Initialize the Signaling engine
Before calling any other Signaling SDK API, initialize an RtmClient object instance.
private boolean createClient(String userId) {
if (userId.isEmpty()) {
showToast("Invalid userId");
return false;
}
try {
RtmConfig config = new RtmConfig.Builder(getString(R.string.app_id), userId)
.eventListener(eventListener)
.build();
mRtmClient = RtmClient.create(config);
return true;
} catch (Exception e) {
showToast("Error creating RTM client.");
return false;
}
}private fun createClient(userId: String): Boolean {
if (userId.isEmpty()) {
showToast("Invalid userId")
return false
}
return try {
// Create a configuration object
val config = RtmConfig.Builder(getString(R.string.app_id), userId)
.eventListener(eventListener)
.build()
// Use the configuration object to instantiate the engine
mRtmClient = RtmClient.create(config)
true
} catch (e: Exception) {
showToast("Error creating RTM client.")
false
}
}Add an event listener
The event listener enables you to implement the processing logic in response to Signaling events. Use the following code to handle event notifications or display received messages:
private RtmEventListener eventListener = new RtmEventListener() {
@Override
public void onMessageEvent(MessageEvent event) {
String text = "Message received from " + event.getPublisherId()
+ " Message: " + event.getMessage().getData();
writeToMessageHistory(text);
}
@Override
public void onPresenceEvent(PresenceEvent event) {
String text = "Received presence event, user: " + event.getPublisherId()
+ " Event: " + event.getEventType();
writeToMessageHistory(text);
}
@Override
public void onLinkStateEvent(LinkStateEvent event) {
String text = "Connection state changed to " + event.getCurrentState()
+ ", Reason: " + event.getReason();
writeToMessageHistory(text);
}
};private val eventListener = object : RtmEventListener {
override fun onMessageEvent(event: MessageEvent) {
val text = "Message received from \${event.publisherId} Message: \${event.message.data}"
writeToMessageHistory(text)
}
override fun onPresenceEvent(event: PresenceEvent) {
val text = "Received presence event, user: \${event.publisherId} event: \${event.eventType}"
writeToMessageHistory(text)
}
override fun onLinkStateEvent(event: LinkStateEvent) {
val text = "Connection state changed to \${event.currentState}, Reason: \${event.reason}"
writeToMessageHistory(text)
}
}Log in to Signaling
To connect to Signaling and access Signaling network resources, such as sending messages, and subscribing to channels, call login.
During a login operation, the client attempts to establish a connection with Signaling. Once the connection is established, the client transmits heartbeat information to the Signaling server at fixed intervals to keep the client active until the client actively logs out or is disconnected. The connection is interrupted when timeout occurs. During this period, users may freely access the Signaling network resources subject to their own permissions and usage restrictions.
private void login(String token) {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
mRtmClient.login(token, new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully logged in to Signaling!");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to log in to Signaling: " + errorInfo);
}
});
}private fun login(token: String) {
mRtmClient?.login(token, object : ResultCallback<Void?> {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Successfully logged in to Signaling!")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to log in to Signaling: $errorInfo")
}
}) ?: showToast("RTM client is null")
}To confirm that login is successful, use the login return value, or listen to the onLinkStateEvent event notification which provides the error code and reason for the login failure. When performing a login operation, the client's network connection state is CONNECTING. After a successful login, the state is updated to CONNECTED.
Best practice
To continuously monitor the network connection state of the client, best practice is to continue to listen for onLinkStateEvent notifications throughout the life cycle of the application. For further details, see Event listeners.
After a user successfully logs into Signaling, the application's PCU increases, which affects your billing data.
Publish a message
To distribute a message to all subscribers of a message channel, call publish. The following code sends a string type message.
private void publishMessage(String channelName, String message) {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
PublishOptions options = new PublishOptions();
options.setCustomType("");
mRtmClient.publish(channelName, message, options, new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Message sent to channel " + channelName + ": " + message);
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to send message to channel " + channelName + ": " + errorInfo);
}
});
}private fun publishMessage(channelName: String, message: String) {
mRtmClient ?: run {
showToast("RTM client is null")
return
}
val options = PublishOptions().apply {
customType = ""
}
mRtmClient!!.publish(channelName, message, options, object : ResultCallback<Void?> {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Message sent to channel $channelName: $message")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to send message to channel $channelName: $errorInfo")
}
})
}Before calling publish to send a message, serialize the message payload as a string.
Subscribe and unsubscribe
To subscribe to a channel, call subscribe. When you subscribe to a channel, you receive all messages published to the channel.
private void subscribe(String channelName) {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
SubscribeOptions options = new SubscribeOptions();
options.setWithMessage(true);
mRtmClient.subscribe(channelName, options, new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully subscribed to the channel!");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to subscribe to the channel: " + errorInfo);
}
});
}private fun subscribe(channelName: String) {
mRtmClient ?: run {
showToast("RTM client is null")
return
}
val options = SubscribeOptions().apply {
withMessage = true
}
mRtmClient!!.subscribe(channelName, options, object : ResultCallback<Void?> {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Successfully subscribed to the channel!")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to subscribe to the channel: $errorInfo")
}
})
}When you no longer need to receive messages from a channel, call unsubscribe to unsubscribe from the channel:
private void unsubscribe(String channelName) {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
mRtmClient.unsubscribe(channelName, new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully unsubscribed from the channel!");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to unsubscribe from the channel: " + errorInfo);
}
});
}private fun unsubscribe(channelName: String) {
mRtmClient ?: run {
showToast("RTM client is null")
return
}
mRtmClient!!.unsubscribe(channelName, object : ResultCallback<Void?> {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Successfully unsubscribed from the channel!")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to unsubscribe from the channel: $errorInfo")
}
})
}For more information about subscribing and sending messages, see Message channels and Stream channels.
Log out of Signaling
When a user no longer needs to use Signaling, call logout. Logging out means closing the connection between the client and Signaling. The user is automatically logged out or unsubscribed from all message and stream channels. Other users in the channel receive an onPresenceEvent notification of the user leaving the channel.
private void logout() {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
mRtmClient.logout(new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully logged out.");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to log out: " + errorInfo);
}
});
// Release resources
mRtmClient.release();
}private fun logout() {
mRtmClient?.logout(object : ResultCallback<Void?> {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Successfully logged out.")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to log out: $errorInfo")
}
}) ?: showToast("RTM client is null")
// Release resources
mRtmClient.release()
}Test Signaling
Take the following steps to test the sample code:
-
Use the Token Builder to generate a Signaling token:
- Select Signaling from the Agora products dropdown.
- Fill in your app ID and app certificate from Agora Console. Leave the remaining fields blank.
- Click Generate Token.
-
In
strings.xml, replace the values forapp_idandtokenwith your app ID and generated token. -
Enable developer options on your Android test device. Turn on USB debugging, connect the Android device to your development machine through a USB cable, and check that your device appears in the Android device options.
-
In Android Studio, click Sync Project with Gradle Files to resolve project dependencies and update the configuration.
-
After synchronization is successful, click ▶️. Android Studio starts compilation. After a few moments, the app is installed and launched on your Android device.
-
Use the device as the receiving end and perform the following operations:
- Enter your User ID and click Login.
- Enter the Channel name and click Subscribe .
-
On a second Android device, repeat the previous steps to install and launch the app. Use this device as the sending end.
- Enter a different User ID and click Login.
- Enter the same Channel name.
- Type a message and click Publish message.
-
Swap the sending and receiving device roles and repeat the previous steps.
Congratulations! You have successfully integrated Signaling into your project.
Reference
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
Token authentication
In this guide you retrieve a temporary token from a Token Builder. To understand how to create an authentication server for development purposes, see Secure authentication with tokens.
Sample projects
Agora provides the following open source sample projects on GitHub for your reference.
Java
Kotlin
Download the projects or view the source code for more detailed examples.
API reference
Understand the tech
To use Signaling features in your app, you initialize a Signaling client instance and add event listeners. To connect to Signaling, you login using an authentication token. To send a message to a message channel, you publish the message. Signaling creates a channel when a user subscribes to it. To receive messages other users publish to a channel, your app listens for events.
To create a pub/sub session for Signaling, implement the following steps in your app:
Prerequisites
To implement the code presented on this page you need to have:
-
Enabled Signaling in Agora Console
-
Xcode 12.0 or higher.
-
A device running iOS 9.0 or higher.
-
Ensure that a firewall is not blocking your network communication.
Signaling 2.x is an enhanced version compared to 1.x with a wide range of new features. It follows a new pricing structure. See Pricing for details.
Project setup
Create a project
In Xcode, create a Single View app under the iOS platform. Configure the project settings as follows:
- Product Name:
RtmQuickstart - Organization Identifier:
agora - User Interface: Storyboard
- Language: Choose Swift or Objective-C
Integrate the SDK
Use either of the following methods to integrate Signaling SDK into your project.
Using CDN
-
Download the latest version of Signaling SDK.
-
Copy the files in the SDK package folder
/libs/AgoraRtmKit.xcframeworkto the project path. -
Open Xcode and navigate to the TARGETS > Project Name > General > Frameworks, Libraries, and Embedded Content menu.
-
Click + > Add Other… > Add Files to add the dynamic library
EmbedAgoraRtmKit.xcframework, and ensure that the Embed property of the added dynamic library is set to Embed & Sign.
Using Cocoapods
-
To follow this procedure, ensure that you have Cocoapods installed. To install Cocoapods, refer to Getting Started with CocoaPods.
-
In the terminal, go to the project root directory and run the
pod initcommand. A text file namedPodfileis generated in the project folder . -
Open the
Podfileand modify the content as follows:platform :ios, '11.0' target 'Your App' do pod 'AgoraRtm', 'x.y.z' endReplace
Your Appwith your target name andx.y.zwith the specific SDK version number, such as 2.2.0. To get the latest version number, check the Release notes.If you are using an SDK version earlier than
2.2.0, change the package name toAgoraRtm_iOS. -
Run the
pod installcommand in the terminal to install the Signaling SDK. You see the message "Pod installation complete!". -
After successful installation, a file with the
.xcworkspacesuffix is generated in the project folder. Open the file in Xcode for subsequent operations.
Using Swift Package Manager Use the following link to integrate the SDK using Swift Package Manager (SPM):
{`https://github.com/AgoraIO/AgoraRtm_Apple.git`}To integrate Signaling SDK version 2.2.0 or above, and Video SDK version 4.3.0 or above at the same time, refer to handle integration issues.
Create a user interface
This section helps you create a simple user interface to explore the basic features of Signaling. Modify it according to your specific needs.
The demo interface consists of the following UI elements:
- Input boxes for user ID, channel name, and message
- Buttons to log in and log out of Signaling
- Buttons to subscribe and unsubscribe from a channel
- A button to publish a message
Sample code to create the user interface
// User Interface
struct ContentView: View {
@StateObject private var viewModel = ChatViewModel()
var body: some View {
VStack {
TextField("Enter username", text: $viewModel.username)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
HStack {
Button("Login") {
viewModel.login()
}
.buttonStyle(LoginButtonStyle())
Button("Logout") {
viewModel.logout()
}
.buttonStyle(LogoutButtonStyle())
}
TextField("Channel name", text: $viewModel.channel)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
HStack {
Button("Subscribe") {
viewModel.subscribe()
}
.buttonStyle(SubscribeButtonStyle())
Button("Unsubscribe") {
viewModel.unsubscribe()
}
.buttonStyle(UnsubscribeButtonStyle())
}
TextField("Enter message", text: $viewModel.message)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
Button("Send") {
viewModel.sendMessage()
}
.buttonStyle(SendButtonStyle())
// Display messages
ScrollViewReader { scrollProxy in
List(viewModel.messages) { message in
Text(message.content)
.id(message.id)
}
.onChange(of: viewModel.messages) { _ in
if let lastMessage = viewModel.messages.last {
withAnimation {
scrollProxy.scrollTo(lastMessage.id, anchor: .bottom)
}
}
}
}
.padding()
}
.padding()
}
}
// Preview
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}Open Main.storyboard using Code View and replace the file contents with the following:
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21679"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="M2K-rz-dlO">
<rect key="frame" x="254" y="103" width="38" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Login"/>
<connections>
<action selector="Login:" destination="BYZ-38-t0r" eventType="touchUpInside" id="UEU-up-ksL"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="CZ1-G5-AkG">
<rect key="frame" x="326" y="103" width="48" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Logout"/>
<connections>
<action selector="Logout:" destination="BYZ-38-t0r" eventType="touchUpInside" id="a0d-8h-eyX"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="0px-1e-aMC">
<rect key="frame" x="242" y="172" width="62" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Subcribe"/>
<connections>
<action selector="SubscribeChannel:" destination="BYZ-38-t0r" eventType="touchUpInside" id="12c-US-VQk"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SDQ-Zt-rBD">
<rect key="frame" x="307" y="172" width="87" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Unsubscribe"/>
<connections>
<action selector="UnsubscribeChannel:" destination="BYZ-38-t0r" eventType="touchUpInside" id="u4T-ze-wtj"/>
</connections>
</button>
<textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="User ID" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="69v-UU-ObH">
<rect key="frame" x="40" y="99" width="187" height="34"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
<textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="Channel name" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="dGk-g2-qHK">
<rect key="frame" x="40" y="168" width="187" height="34"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Qqk-6X-Tcb">
<rect key="frame" x="304" y="247" width="88" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Publish MSG"/>
<connections>
<action selector="SendMessageToMessageChannel:" destination="BYZ-38-t0r" eventType="touchUpInside" id="XcX-nz-fPl"/>
</connections>
</button>
<textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="Message content" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="lKg-ap-M6p">
<rect key="frame" x="40" y="245" width="252" height="34"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" fixedFrame="YES" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="e1v-i8-spC">
<rect key="frame" x="40" y="416" width="330" height="428"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<color key="textColor" systemColor="labelColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
</subviews>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
<connections>
<outlet property="ChannelIDTextField" destination="dGk-g2-qHK" id="Iuh-ow-1MH"/>
<outlet property="GroupMsgButton" destination="Qqk-6X-Tcb" id="TV4-sg-bY7"/>
<outlet property="GroupMsgTextField" destination="lKg-ap-M6p" id="2Me-Z0-QIH"/>
<outlet property="LoginButton" destination="M2K-rz-dlO" id="ZxJ-oV-UKy"/>
<outlet property="LogoutButton" destination="CZ1-G5-AkG" id="ZQE-B7-yya"/>
<outlet property="MsgTextView" destination="e1v-i8-spC" id="76J-8Q-kKp"/>
<outlet property="SubsctibeButton" destination="0px-1e-aMC" id="9TL-8M-l6w"/>
<outlet property="UnSubscribeButton" destination="SDQ-Zt-rBD" id="tgd-fK-EfO"/>
<outlet property="UserIDTextField" destination="69v-UU-ObH" id="3BH-ci-t7A"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="131.8840579710145" y="97.767857142857139"/>
</scene>
</scenes>
<resources>
<systemColor name="labelColor">
<color red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>Open the file ViewController.h and replace the contents with the following:
#import <UIKit/UIKit.h>
#import <AgoraRtmKit/AgoraRtmKit.h>
@interface ViewController : UIViewController
// Buttons
@property (weak, nonatomic) IBOutlet UIButton *LoginButton;
@property (weak, nonatomic) IBOutlet UIButton *LogoutButton;
@property (weak, nonatomic) IBOutlet UIButton *SubsctibeButton;
@property (weak, nonatomic) IBOutlet UIButton *UnSubscribeButton;
@property (weak, nonatomic) IBOutlet UIButton *GroupMsgButton;
// TextFields
@property (weak, nonatomic) IBOutlet UITextField *UserIDTextField;
@property (weak, nonatomic) IBOutlet UITextField *ChannelIDTextField;
@property (weak, nonatomic) IBOutlet UITextField *GroupMsgTextField;
@property (weak, nonatomic) IBOutlet UITextView *MsgTextView;
@endImplement Signaling
A complete code sample that implements the basic features of Signaling is presented here for your reference. To use the sample code, open ViewController.m and replace the contents with the following:
Complete sample code for real-time Signaling
import SwiftUI
import Combine
import AgoraRtmKit
struct Message: Identifiable, Equatable {
let id = UUID()
let content: String
}
class ChatViewModel: NSObject, ObservableObject, AgoraRtmClientDelegate {
var appid: String = <#YOUR APPID#>
var token: String = <#YOUR TOKEN#>
var rtmKit: AgoraRtmClientKit? = nil
@Published var username: String = ""
@Published var message: String = ""
@Published var channel: String = ""
@Published var messages: [Message] = []
override init() {
super.init()
initializeEngine()
}
// Initialization the engine
func initializeEngine() {
if rtmKit == nil {
let config = AgoraRtmClientConfig(appId: appid)
rtmKit = try? AgoraRtmClientKit(config, delegate: self)
}
}
// Log in to Signaling
func login() {
if rtmKit != nil {
addToMessageList(str: "RTM already logged in! Logout first!")
return
}
let config = AgoraRtmClientConfig(appId: appid, userId: username)
rtmKit = try! AgoraRtmClientKit(config, delegate: self)
rtmKit?.login(token, userId: username, completion: { response, error in
if let error = error {
self.addToMessageList(str: "Login failed. Error code: \(error.errorCode.rawValue), reason: \(error.reason)")
} else {
self.addToMessageList(str: "\(self.username) logged in successfully.")
}
})
}
// Log out from the RTM server
func logout() {
guard let rtmKit = rtmKit else {
addToMessageList(str: "RTM is already logged out!")
return
}
rtmKit.logout()
rtmKit.destroy()
self.rtmKit = nil
addToMessageList(str: "RTM logged out!")
}
// Subscribe to a channel
func subscribe() {
guard let rtmKit = rtmKit else { return }
rtmKit.subscribe(channelName: channel, option: nil) { response, error in
if let error = error {
self.addToMessageList(str: "Subscribe to channel '\(self.channel)' failed. Error code: \(error.errorCode.rawValue), reason: \(error.reason)")
} else {
self.addToMessageList(str: "Subscribed to channel: \(self.channel) successfully.")
}
}
}
// Unsubscribe from a channel
func unsubscribe() {
guard let rtmKit = rtmKit else { return }
rtmKit.unsubscribe(channel) { response, error in
if let error = error {
self.addToMessageList(str: "Unsubscribe from channel '\(self.channel)' failed. Error code: \(error.errorCode.rawValue), reason: \(error.reason)")
} else {
self.addToMessageList(str: "Unsubscribed from channel: \(self.channel) successfully.")
}
}
}
// Publish a message
func sendMessage() {
guard !message.isEmpty, let rtmKit = rtmKit else { return }
rtmKit.publish(channelName: channel, message: message, option: nil) { response, error in
if let error = error {
self.addToMessageList(str: "Publish failed. Error code: \(error.errorCode.rawValue), reason: \(error.reason)")
} else {
self.addToMessageList(str: "Message published to channel: \(self.channel) successfully.")
}
}
message = ""
}
func addToMessageList(str: String) {
messages.append(Message(content: str))
}
// AgoraRtmClientDelegate methods
func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveLinkStateEvent event: AgoraRtmLinkStateEvent) {
addToMessageList(str: "RTM link state changed. Current state: \(event.currentState.rawValue), previous state: \(event.previousState.rawValue)")
}
func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveMessageEvent event: AgoraRtmMessageEvent) {
addToMessageList(str: "Message received. Channel: \(event.channelName), Publisher: \(event.publisher), Message: \(event.message.stringData!)")
}
}
// User Interface
struct ContentView: View {
@StateObject private var viewModel = ChatViewModel()
var body: some View {
VStack {
TextField("Enter username", text: $viewModel.username)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
HStack {
Button("Login") {
viewModel.login()
}
Button("Logout") {
viewModel.logout()
}
}
TextField("Channel name", text: $viewModel.channel)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
HStack {
Button("Subscribe") {
viewModel.subscribe()
}
Button("Unsubscribe") {
viewModel.unsubscribe()
}
}
TextField("Enter message", text: $viewModel.message)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
Button("Send") {
viewModel.sendMessage()
}
.buttonStyle(SendButtonStyle())
// Display messages
ScrollViewReader { scrollProxy in
List(viewModel.messages) { message in
Text(message.content)
.id(message.id)
}
.onChange(of: viewModel.messages) { _ in
if let lastMessage = viewModel.messages.last {
withAnimation {
scrollProxy.scrollTo(lastMessage.id, anchor: .bottom)
}
}
}
}
.padding()
}
.padding()
}
}
// Preview
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}#import "ViewController.h"
@interface ViewController ()<AgoraRtmClientDelegate>
@property(nonatomic, strong) AgoraRtmClientKit* kit;
@property NSString* appID;
@property NSString* token;
@property NSString* uid;
@property NSString* peerID;
@property NSString* channelID;
@property NSString* peerMsg;
@property NSString* channelMsg;
@property NSString* text;
@property NSMutableArray* textArray;
- (void)AddMsgToRecord:(NSString*)text;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Enter your App ID
self.appID = @"your_appid";
self.MsgTextView.textColor = UIColor.blueColor;
self.textArray = [[NSMutableArray alloc]init];
}
- (void)rtmKit:(AgoraRtmClientKit *)rtmKit didReceiveMessageEvent:(AgoraRtmMessageEvent *)event {
NSLog(@"%@", self.text);
self.text = [NSString stringWithFormat:@"receive message
From channel: %@
publisher:%@
message:%@
", event.channelName, event.publisher, event.message.stringData];
[self AddMsgToRecord:(self.text)];
}
// Add message to the UI TextView
- (void)AddMsgToRecord:(NSString*)text {
[self.textArray addObject:(self.text)];
self.MsgTextView.text = [self.textArray componentsJoinedByString:(@"
")];
}
- (IBAction)Login:(id)sender {
self.uid = self.UserIDTextField.text;
// Enter your token
self.token = @"your_token";
AgoraRtmClientConfig* rtm_config = [[AgoraRtmClientConfig alloc] initWithAppId:_appID userId:_uid];
NSError* initError = nil;
_kit = [[AgoraRtmClientKit alloc] initWithConfig:rtm_config delegate:self error:&initError];
if (initError != nil) {
self.text = [NSString stringWithFormat:@"init error %@",initError];
NSLog(@"%@", self.text);
[self AddMsgToRecord:(self.text)];
}
// Log in to the RTM server
[_kit loginByToken:self.token completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo.errorCode != AgoraRtmErrorOk) {
self.text = [NSString stringWithFormat:@"Login failed for user %@. Code: %ld", self.uid, (long)errorInfo.errorCode];
NSLog(@"%@", self.text);
} else {
self.text = [NSString stringWithFormat:@"Login successful for user %@. Code: %ld", self.uid, (long)errorInfo.errorCode];
NSLog(@"%@", self.text);
}
[self AddMsgToRecord:(self.text)];
}];
}
- (IBAction)Logout:(id)sender {
if (_kit != nil) {
// Log out from the RTM server
[_kit logout:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
self.text = [NSString stringWithFormat:@"Logout successful"];
NSLog(@"%@", self.text);
[_kit destroy];
_kit = nil;
} else {
self.text = [NSString stringWithFormat:@"Logout failed. Code: %ld", (long)errorInfo.errorCode];
NSLog(@"%@", self.text);
}
[self AddMsgToRecord:(self.text)];
}];
}
}
- (IBAction)SubscribeChannel:(id)sender {
self.channelID = self.ChannelIDTextField.text;
if (_kit != nil) {
// Subscribe to a channel
[_kit subscribeWithChannel:self.channelID option:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
self.text = [NSString stringWithFormat:@"Successfully subscribed to channel %@", self.channelID];
NSLog(@"%@", self.text);
} else {
self.text = [NSString stringWithFormat:@"Failed to subscribe to channel %@. Code: %ld", self.channelID, (long)errorInfo.errorCode];
NSLog(@"%@", self.text);
}
[self AddMsgToRecord:(self.text)];
}];
}
}
- (IBAction)UnsubscribeChannel:(id)sender {
if (_kit == nil) return;
// Unsubscribe from a channel
[_kit unsubscribeWithChannel:self.channelID completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
self.text = [NSString stringWithFormat:@"Successfully unsubscribed from channel"];
} else {
self.text = [NSString stringWithFormat:@"Failed to unsubscribe from channel. Code: %ld", (long)errorInfo.errorCode];
}
[self AddMsgToRecord:(self.text)];
}];
}
- (IBAction)SendMessageToMessageChannel:(id)sender {
self.channelID = self.ChannelIDTextField.text;
self.channelMsg = self.GroupMsgTextField.text;
// Publish a message
[_kit publish:self.channelID message:self.channelMsg option:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
self.text = [NSString stringWithFormat:@"Message sent to channel %@ : %@", self.channelID, self.channelMsg];
} else {
self.text = [NSString stringWithFormat:@"Message failed to send to channel %@ : %@. ErrorCode: %ld", self.channelID, self.channelMsg, (long)errorInfo.errorCode];
}
[self AddMsgToRecord:(self.text)];
}];
}
@endFollow the implementation steps to understand the core API calls in the sample code or use the snippets in your own code.
Declare the variables you need
To connect to Signaling from your project and send messages to a channel, declare variables to hold the app ID, token, user ID, and channel name:
var appid: String = <#YOUR APPID#>
var token: String = <#YOUR TOKEN#>
var rtmKit: AgoraRtmClientKit? = nil
@Published var username: String = ""
@Published var message: String = ""
@Published var channel: String = ""
@Published var messages: [Message] = []#import "ViewController.h"
@interface ViewController ()<AgoraRtmClientDelegate>
@property(nonatomic, strong)AgoraRtmClientKit* kit;
@property NSString* appID;
@property NSString* token;
@property NSString* uid;
@property NSString* peerID;
@property NSString* channelID;
@property NSString* peerMsg;
@property NSString* channelMsg;
@property NSString* text;
@property NSMutableArray* textArray;Initialize the Signaling engine
Before calling any other Signaling SDK API, initialize an AgoraRtmClientKit object instance.
// Initialization the engine
func initializeEngine() {
if rtmKit == nil {
let config = AgoraRtmClientConfig(appId: appid)
rtmKit = try? AgoraRtmClientKit(config, delegate: self)
}
}self.uid = self.UserIDTextField.text;
// Set engine configuration
AgoraRtmClientConfig* rtm_config = [[AgoraRtmClientConfig alloc] initWithAppId:_appID userId:_uid];
// Initialize the engine
NSError* initError = nil;
_kit = [[AgoraRtmClientKit alloc] initWithConfig:rtm_config delegate:self error:&initError];
if (initError != nil) {
self.text = [NSString stringWithFormat:@"init error %@",initError];
NSLog(@"%@", self.text);
[self AddMsgToRecord:(self.text)];
}Add an event listener
The event listener enables you to implement the processing logic in response to Signaling events. Use the following code to handle event notifications or display received messages:
// Add the event listener
func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveLinkStateEvent event: AgoraRtmLinkStateEvent) {
addToMessageList(str: "Signaling link state change current state is: \(event.currentState.rawValue) previous state is :\(event.previousState.rawValue)")
}
func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveMessageEvent event: AgoraRtmMessageEvent) {
addToMessageList(str: "Message received.
channel: \(event.channelName), publisher: \(event.publisher), message content: \(event.message.stringData!)")
}// Add the event listener
- (void)rtmKit:(AgoraRtmClientKit *)rtmKit didReceiveMessageEvent:(AgoraRtmMessageEvent *)event {
NSLog(@"%@", self.text);
self.text = [NSString stringWithFormat:@"receive message
From channel: %@
publisher:%@
message:%@
",event.channelName,event.publisher, event.message.stringData];
[self AddMsgToRecord:(self.text)];
}Log in to Signaling
To connect to Signaling and access Signaling network resources, such as sending messages, and subscribing to channels, login to Signaling server.
During a login operation, the client attempts to establish a connection with Signaling. Once the connection is established, the client transmits heartbeat information to the Signaling server at fixed intervals to keep the client active until the client actively logs out or is disconnected. The connection is interrupted when timeout occurs. During this period, users may freely access the Signaling network resources subject to their own permissions and usage restrictions.
// Log in to the Signaling server
func login() {
if rtmKit != nil {
addToMessageList(str: "RTM already logged in! Logout first!")
return
}
let config = AgoraRtmClientConfig(appId: appid, userId: username)
rtmKit = try! AgoraRtmClientKit(config, delegate: self)
rtmKit?.login(token, userId: username, completion: { response, error in
if let error = error {
self.addToMessageList(str: "Login failed. Error code: \(error.errorCode.rawValue), reason: \(error.reason)")
} else {
self.addToMessageList(str: "\(self.username) logged in successfully.")
}
})
}// Log in to signaling
[_kit loginByToken:self.token completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo.errorCode != AgoraRtmErrorOk){
self.text = [NSString stringWithFormat:@"Login failed for user %@. Code: %ld",self.uid, (long)errorInfo.errorCode];
NSLog(@"%@", self.text);
} else {
NSLog(@"%@", self.text);
self.text = [NSString stringWithFormat:@"Login successful for user %@. Code: %ld",self.uid, (long)errorInfo.errorCode];
}
[self AddMsgToRecord:(self.text)];
}];Use the login return value, or listen to the didReceiveLinkStateEvent event notification to confirm that login is successful or obtain the error code and cause of login failure. When performing a login operation, the client's network connection state is connecting. After a successful login, the state is updated to Connected.
Best practice
To continuously monitor the network connection state of the client, best practice is to continue to listen for AgoraRtmLinkState event notifications throughout the life cycle of the application. For further details, see Event listeners.
Use the loginByToken return value, or listen to the connectionChangedToState event notification to confirm that login is successful or obtain the error code and cause of login failure. When performing a login operation, the client's network connection state is AgoraRtmClientConnectionStateConnecting. After a successful login, the state is updated to AgoraRtmClientConnectionStateConnected.
Best practice
To continuously monitor the network connection state of the client, best practice is to continue to listen for connectionChangedToState event notifications throughout the life cycle of the application. For further details, see Event listeners.
After a user successfully logs into Signaling, the application's Peak Connection Usage (PCU) increases, which affects your billing data.
Send a message
To distribute a message to all subscribers of a message channel, call publish. The following code sends a string type message.
// Publish a message
func sendMessage() {
guard !message.isEmpty, let rtmKit = rtmKit else { return }
rtmKit.publish(channelName: channel, message: message, option: nil) { response, error in
if let error = error {
self.addToMessageList(str: "Publish failed. Error code: (error.errorCode.rawValue), reason: (error.reason)")
} else {
self.addToMessageList(str: "Message published to channel: (self.channel) successfully.")
}
}
message = ""
}// Send a message
[_kit publish:self.channelID message:self.channelMsg option:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
self.text = [NSString stringWithFormat:@"Message sent to channel %@ : %@", self.channelID, self.channelMsg];
} else {
self.text = [NSString stringWithFormat:@"Message failed to send to channel %@ : %@ ErrorCode: %ld", self.channelID, self.channelMsg, (long)errorInfo.errorCode];
}
[self AddMsgToRecord:(self.text)];
}];Subscribe and unsubscribe
To receive messages sent to a channel, subscribe to channel messages:
// Subscribe to a channel
func subscribe() {
guard let rtmKit = rtmKit else { return }
rtmKit.subscribe(channelName: channel, option: nil) { response, error in
if let error = error {
self.addToMessageList(str: "Subscribe to channel '(self.channel)' failed. Error code: (error.errorCode.rawValue), reason: (error.reason)")
} else {
self.addToMessageList(str: "Subscribed to channel: (self.channel) successfully.")
}
}
}// Subscribe to a channel
[_kit subscribeWithChannel:self.channelID option:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if(errorInfo == nil) {
self.text = [NSString stringWithFormat:@"Successfully subscribe channel %@",self.channelID];
NSLog(@"%@", self.text);
} else {
self.text = [NSString stringWithFormat:@"Failed to subscribe channel %@ Code: %ld",self.channelID, (long)errorInfo.errorCode];
NSLog(@"%@", self.text);
}
[self AddMsgToRecord:(self.text)];
}];When you no longer need to receive messages from a channel, unsubscribe from the channel:
// Unsubscribe from a channel
func unsubscribe() {
guard let rtmKit = rtmKit else { return }
rtmKit.unsubscribe(channel) { response, error in
if let error = error {
self.addToMessageList(str: "Unsubscribe from channel '(self.channel)' failed. Error code: (error.errorCode.rawValue), reason: (error.reason)")
} else {
self.addToMessageList(str: "Unsubscribed from channel: (self.channel) successfully.")
}
}
}// Unsubscribe from a channel
[_kit unsubscribeWithChannel:self.channelID completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil){
self.text = [NSString stringWithFormat:@"Leave channel successful"];
} else {
self.text = [NSString stringWithFormat:@"Failed to leave channel Code: %ld", (long)errorInfo.errorCode];
}
[self AddMsgToRecord:(self.text)];
}];For more information about sending and receiving messages see Message channels and Stream channels.
Log out of Signaling
When a user no longer needs to use Signaling, call logout. Logging out means closing the connection between the client and Signaling. The user is automatically logged out or unsubscribed from all message and stream channels. Other users in the channel receive a didReceivePresenceEvent notification of the user leaving the channel.
// Log out from the Signaling server
func logout() {
guard let rtmKit = rtmKit else {
addToMessageList(str: "Signaling is already logged out!")
return
}
rtmKit.logout()
rtmKit.destroy()
self.rtmKit = nil
addToMessageList(str: "Signaling logged out!")
}// Log out of Signaling
[_kit logout:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil){
self.text = [NSString stringWithFormat:@"Logout successful"];
NSLog(@"%@", self.text);
[_kit destroy];
_kit = nil;
} else {
self.text = [NSString stringWithFormat:@"Logout failed. Code: %ld",(long)errorInfo.errorCode];
NSLog(@"%@", self.text);
}
[self AddMsgToRecord:(self.text)];
}];
// Clean up
[_kit destroy];
_kit = nil;Test Signaling
Take the following steps to test the sample code:
-
Use the Token Builder to generate a Signaling token:
- Select Signaling from the Agora products dropdown.
- Fill in your app ID and app certificate from Agora Console. Leave the remaining fields blank.
- Click Generate Token.
-
In your code, replace the values for
appIDandtokenwith your app ID and the generated token. -
Choose the device or simulator you want to run the app on from the device selection dropdown menu in the Xcode toolbar.
-
Click Run in the Xcode toolbar, or press Command + R on your keyboard. Xcode builds your project and launches the app on the selected device or simulator.
-
Use the device as the receiving end and perform the following operations:
- Enter your User ID and click Login.
- Enter the Channel name and click Subscribe .
-
On a second device, repeat the previous steps to install and launch the app. Use this device as the sending end.
- Enter a different User ID and click Login.
- Enter the same Channel name.
- Type a message and click Publish message.
-
Swap the sending and receiving device roles and repeat the previous steps.
Congratulations! You have successfully integrated Signaling into your project.
Reference
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
Token authentication
In this guide you retrieve a temporary token from a Token Builder. To understand how to create an authentication server for development purposes, see Secure authentication with tokens.
Sample projects
Agora provides the following open source sample projects on GitHub for your reference.
API reference
Swift
Objective-C
Understand the tech
To use Signaling features in your app, you initialize a Signaling client instance and add event listeners. To connect to Signaling, you login using an authentication token. To send a message to a message channel, you publish the message. Signaling creates a channel when a user subscribes to it. To receive messages other users publish to a channel, your app listens for events.
To create a pub/sub session for Signaling, implement the following steps in your app:
Prerequisites
To implement the code presented on this page you need to have:
-
Enabled Signaling in Agora Console
-
A JavaScript package manager such as npm.
-
Ensure that a firewall is not blocking your network communication.
Signaling 2.x is an enhanced version compared to 1.x with a wide range of new features. It follows a new pricing structure. See Pricing for details.
Project setup
Create a project
To create a folder for your project, and an index.html file for your app, execute the following commands in the terminal:
mkdir HelloWorld
cd HelloWorld
touch index.htmlIntegrate the SDK
Use either of the following methods to integrate Signaling SDK into your project.
Using CDN
-
Download the latest version of Signaling SDK for Web.
-
Add the following code to your project to reference the SDK:
<script src="path_to_sdk/agora-rtm.x.y.z.min.js"></script>Replace
x.y.zwith the specific SDK version number, such as 2.2.0. To get the latest version number, check the Release notes.
Using npm
-
Install the SDK using npm
npm install agora-rtm-sdk -
Import
AgoraRTMfrom the SDKimport AgoraRTM from 'agora-rtm-sdk';
Implement Signaling
A complete code sample that implements the basic features of Signaling is presented here for your reference. To use the sample code, copy the following lines into the index.html file:
Complete sample code for Signaling
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Text Display App</title>
<style>
#container {
width: 800px;
margin: 0 auto;
padding: 20px;
}
#textDisplay {
width: 100%;
height: 800px;
border: 1px solid #b0b0b0;
margin-bottom: 20px;
overflow: auto;
text-align: left;
padding: 10px;
box-sizing: border-box;
}
#inputContainer {
display: flex;
align-items: center;
}
#textInput {
width: calc(100% - 100px);
padding: 5px;
margin-right: 10px;
}
#submitButton {
width: 90px;
padding: 5px;
}
</style>
<script src="./AgoraRTM-production.js"></script>
</head>
<body>
<script>
const { RTM } = AgoraRTM;
// Fill in the App ID of your project.
const appId = "your_appId";
// Fill in your user ID.
const userId = "your_userId";
// Fill in your channel name.
const msChannelName = "Chat_room";
const buttonClick = () => {
var input = document.getElementById("textInput");
publishMessage(input.value);
input.value = '';
}
const setupRTM = async () => {
// Initialize the RTM client.
try {
rtm = new RTM(appId, userId);
} catch (status) {
console.log("Error");
console.log(status);
}
// Add the event listener.
// Message event handler.
rtm.addEventListener("message", event => {
showMessage(event.publisher, event.message);
});
// Presence event handler.
rtm.addEventListener("presence", event => {
if (event.eventType === "SNAPSHOT") {
showMessage("INFO", "I Join");
}
else {
showMessage("INFO", event.publisher + " is " + event.eventType);
}
});
// Connection state changed event handler.
rtm.addEventListener("status", event => {
// The current connection state.
const currentState = event.state;
// The reason why the connection state changes.
const changeReason = event.reason;
showMessage("INFO", JSON.stringify(event));
});
// Log in the RTM server.
try {
const result = await rtm.login({ token: 'your_token' });
console.log(result);
} catch (status) {
console.log(status);
}
// Subscribe to a channel.
try {
const result = await rtm.subscribe(msChannelName);
console.log(result);
} catch (status) {
console.log(status);
}
}
const publishMessage = async (message) => {
const payload = { type: "text", message: message };
const publishMessage = JSON.stringify(payload);
const publishOptions = { channelType: 'MESSAGE'}
try {
const result = await rtm.publish(msChannelName, publishMessage, publishOptions);
showMessage(userId, publishMessage);
console.log(result);
} catch (status) {
console.log(status);
}
}
const showMessage = (user, msg) => {
// Get text from the text box.
const inputText = textInput.value;
const newText = document.createTextNode(user + ": " + msg);
const newLine = document.createElement("br");
textDisplay.appendChild(newText);
textDisplay.appendChild(newLine);
}
window.onload = setupRTM;
</script>
<div id="container">
<h1>Hello RTM !</h1>
<div>
<div id="textDisplay"></div>
</div>
<div id="inputContainer">
<input type="text" id="textInput" placeholder="Enter text">
<button id="submitButton" onclick="buttonClick()"> Send </button>
</div>
</div>
</body>
</html>Follow the implementation steps to understand the core API calls in the sample code or use the snippets in your own code.
Initialize the Signaling engine
Before calling any other Signaling SDK API, initialize an RTM object instance.
try {
const rtm = new RTM(appId, userId);
} catch (status) {
console.log("Error");
console.log(status);
}Add an event listener
The event listener enables you to implement the processing logic in response to Signaling events. Use the following code to handle event notifications or display received messages:
// Message event handler.
rtm.addEventListener("message", event => {
showMessage(event.publisher, event.message);
});
// Presence event handler.
rtm.addEventListener("presence", event => {
if (event.eventType === "SNAPSHOT") {
showMessage("INFO", "I Join");
}
else {
showMessage("INFO", event.publisher + " is " + event.eventType);
}
});
// Connection state changed event handler.
rtm.addEventListener("status", event => {
// The current connection state.
const currentState = event.state;
// The reason why the connection state changes.
const changeReason = event.reason;
showMessage("INFO", JSON.stringify(event));
});Log in to Signaling
To connect to Signaling and access Signaling network resources, such as sending messages, and subscribing to channels, call login.
During a login operation, the client attempts to establish a connection with Signaling. Once the connection is established, the client transmits heartbeat information to the Signaling server at fixed intervals to keep the client active until the client actively logs out or is disconnected. The connection is interrupted when timeout occurs. During this period, users may freely access the Signaling network resources subject to their own permissions and usage restrictions.
// Log in to Signaling
try {
const result = await rtm.login({ token: 'your_token' });
console.log(result);
} catch (status) {
console.log(status);
}Use the login return value, or listen to the status event notification to confirm that login is successful or obtain the error code and cause of login failure. When performing a login operation, the client's network connection state is CONNECTING. After a successful login, the state is updated to CONNECTED.
Best practice
To continuously monitor the network connection state of the client, best practice is to continue to listen for status event notifications throughout the life cycle of the application. For further details, see Event listeners.
After a user successfully logs into Signaling, the application's PCU increases, which affects your billing data.
Send a message
To distribute a message to all subscribers of a message channel, call publish. The following code sends a string type message.
// Send a message to a channel
const publishMessage = async (message) => {
const payload = { type: "text", message: message };
const publishMessage = JSON.stringify(payload);
const publishOptions = { channelType: 'MESSAGE'}
try {
const result = await rtm.publish(msChannelName, publishMessage, publishOptions);
showMessage(userId, publishMessage);
console.log(result);
} catch (status) {
console.log(status);
}
}Before calling publish to send a message, serialize the message payload as a string.
Subscribe and unsubscribe
To receive messages sent to a channel, call subscribe to subscribe to channel messages:
// Subscribe to a channel
try {
const result = await rtm.subscribe(msChannelName);
console.log(result);
} catch (status) {
console.log(status);
}When you no longer need to receive messages from a channel, call unsubscribe to unsubscribe from the channel:
// Unsubscribe from a channel
try {
const result = await rtm.unsubscribe(msChannelName);
console.log(result);
} catch (status) {
console.log(status);
}
For more information about sending and receiving messages see Message channels and Stream channels.
Log out of Signaling
When a user no longer needs to use Signaling, call logout. Logging out means closing the connection between the client and Signaling. The user is automatically logged out or unsubscribed from all message and stream channels. Other users in the channel receive a presence notification of the user leaving the channel.
// Logout of Signaling
try {
const result = await rtm.logout();
} catch (status) {
const { operation, reason, errorCode } = status;
console.log(`${operation} failed, the error code is ${errorCode}, because of: ${reason}.`);
}Test Signaling
Take the following steps to test the sample code:
-
Use the Token Builder to generate a Signaling token:
- Select Signaling from the Agora products dropdown.
- Fill in your app ID and app certificate from Agora Console. Leave the remaining fields blank.
- Click Generate Token.
-
In
index.html, replaceyour_appidandyour_tokenvalues in the code with your project's app ID and the generated token. -
Update the value for the
userIdwith an integer value. -
Save the file and run it in your browser.
-
Make a copy of the project. Use the same
app_idbut a differentuserId. Launch another instance of the page in a separate browser tab. -
Type a message in the text input box in either instance and click the Send button. The message is displayed in the other app instance.
-
Swap the sending and receiving instances and send another message.
Congratulations! You have successfully integrated Signaling into your project.
Reference
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
Token authentication
In this guide you retrieve a temporary token from a Token Builder. To understand how to create an authentication server for development purposes, see Secure authentication with tokens.
Sample project
Explore and experiment with the Signaling Online demo. To download and customize the source code, refer to the GitHub repository.
API reference
Understand the tech
To use Signaling features in your app, you initialize a Signaling client instance and add event listeners. To connect to Signaling, you login using an authentication token. To send a message to a message channel, you publish the message. Signaling creates a channel when a user subscribes to it. To receive messages other users publish to a channel, your app listens for events.
To create a pub/sub session for Signaling, implement the following steps in your app:
Prerequisites
To implement the code presented on this page you need to have:
-
Enabled Signaling in Agora Console
-
Flutter 2.0.0 or higher
-
Dart 2.15.1 or higher
-
Android Studio, IntelliJ, VS Code, or any other IDE that supports Flutter, see Set up an editor.
-
If your target platform is iOS:
- Xcode on macOS (latest version recommended)
- A physical iOS device
- iOS version 12.0 or later
-
If your target platform is Android:
- Android Studio on macOS or Windows (latest version recommended)
- An Android emulator or a physical Android device.
-
If you are developing a desktop application for Windows, macOS or Linux, make sure your development device meets the Flutter desktop development requirements.
-
Ensure that a firewall is not blocking your network communication.
Signaling 2.x is an enhanced version compared to 1.x with a wide range of new features. It follows a new pricing structure. See Pricing for details.
Project setup
To create a new Flutter project, follow these steps:
-
Open a terminal and execute the following command:
flutter create sdk_quickstart -
Open the project in your IDE. Add the Signaling SDK dependency to
pubspec.yamlunderdependencies:dependencies: # Add the Signaling SDK dependency, use the latest version number agora_rtm: ^2.2.1To integrate Signaling SDK version 2.2.1 or above, and Video SDK version 4.3.0 or above at the same time, refer to handle integration issues.
-
To install the dependencies, open the terminal and execute the following command in the project path:
flutter pub get -
To create a basic template for your project, open
main.dart, delete the contents of the file, and paste the following code:import 'package:agora_rtm/agora_rtm.dart'; import 'package:flutter/material.dart'; import 'dart:convert'; void main() async { // Wait for Widgets to be initialized. WidgetsFlutterBinding.ensureInitialized(); // Create a Signaling client instance // Login to Signaling // Subscribe to a channel // Publish messages // Unsubscribe from a channel // Logout from Signaling }
Implement Signaling
A complete code sample that implements the basic features of Signaling is presented here for your reference. To use the sample code, copy the following lines into the lib/main.dart file.
Complete sample code
import 'package:flutter/material.dart';
import 'package:agora_rtm/agora_rtm.dart';
import 'dart:convert';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
const userId = 'your_userId';
const appId = 'your_appId';
const token = 'your_token';
const channelName = 'getting-started';
late RtmClient rtmClient;
// Create a Signaling client instance
try {
// Create rtm client
final (status, client) = await RTM( appId, userId);
if (status.error == true) {
print('\${status.operation} failed due to \${status.reason}, error code: \${status.errorCode}');
} else {
rtmClient = client;
print('Initialize success!');
}
// Add events listener
rtmClient.addListener(
// Add message event handler
message: (event) {
print('received a message from channel: \${event.channelName}, channel type : \${event.channelType}');
print('message content: \${utf8.decode(event.message!)}, custom type: \${event.customType}');
},
// add link state event handler
linkState: (event) {
print('link state changed from \${event.previousState} to \${event.currentState}');
print('reason: \${event.reason}, due to operation \${event.operation}');
});
} catch (e) {
print('Initialize failed!:\${e}');
}
// Login to Signaling
try {
// login rtm service
var (status,response) = await rtmClient.login(token);
if (status.error == true) {
print('\${status.operation} failed due to \${status.reason}, error code: \${status.errorCode}');
} else {
print('login RTM success!');
}
} catch (e) {
print('Failed to login: $e');
}
// Subscribe to a channel
try {
var (status,response) = await rtmClient.subscribe(channelName);
if (status.error == true) {
print('\${status.operation} failed due to \${status.reason}, error code: \${status.errorCode}');
} else {
print('subscribe channel: \${channelName} success!');
}
} catch (e) {
print('Failed to subscribe channel: $e');
}
// Publish messages
// Send a message every second for 100 seconds
for (var i = 0; i < 100; i++) {
try {
var (status, response) = await rtmClient.publish(
channelName,
'message number : $i',
channelType: RtmChannelType.message,
customType: 'PlainText'
);
if (status.error == true ){
print('\${status.operation} failed, errorCode: \${status.errorCode}, due to \${status.reason}');
} else {
print('\${status.operation} success! message number:$i');
}
} catch (e) {
print('Failed to publish message: $e');
}
await Future.delayed(Duration(seconds: 1));
}
// Unsubscribe from a channel
try {
var (status,response) = await rtmClient.unsubscribe(channelName);
if (status.error == true) {
print('\${status.operation} failed due to \${status.reason}, error code: \${status.errorCode}');
} else {
print('unsubscribe success!');
}
} catch (e) {
print('something went wrong with logout: $e');
}
// Logout of Signaling
try {
// logout rtm service
var (status,response) = await rtmClient.logout();
if (status.error == true) {
print('\${status.operation} failed due to \${status.reason}, error code: \${status.errorCode}');
} else {
print('logout RTM success!');
}
} catch (e) {
print('something went wrong with logout: $e');
}
}Follow the implementation steps to understand the core API calls in the sample code or to build the demo step-by-step.
Initialize the Signaling engine
Before calling any other Signaling SDK API, initialize an RtmClient object instance. Add the following code to the lib/main.dart file.
import 'package:agora_rtm/agora_rtm.dart';
import 'package:flutter/material.dart';
import 'dart:convert';
void main() async {
// Wait for Widgets to be initialized.
WidgetsFlutterBinding.ensureInitialized();
const userId = 'your_userId';
const appId = 'your_appId';
const token = 'your_token';
const channelName = 'getting-started';
late RtmClient rtmClient;
try {
// Create rtm client
final (status, client) = await RTM( appId, userId);
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
rtmClient = client;
print('Initialize success!');
}
// Add events listener
} catch (e) {
print('Initialize failed!:${e}');
}
}Add an event listener
The event listener enables you to implement the processing logic in response to Signaling events. Use the following code to handle event notifications or display received messages:
// Paste the following code snippet below the "add event listener" comment
rtmClient.addListener(
// Add message event handler
message: (event) {
print('received a message from channel: ${event.channelName}, channel type : ${event.channelType}');
print('message content: ${utf8.decode(event.message!)}, custome type: ${event.customType}');
},
// Add linkState event handler
linkState: (event) {
print('link state changed from ${event.previousState} to ${event.currentState}');
print('reason: ${event.reason}, due to operation ${event.operation}');
});Log in to Signaling
To connect to Signaling and access Signaling network resources, such as sending messages, and subscribing to channels, call login.
During a login operation, the client attempts to establish a connection with Signaling. Once the connection is established, the client transmits heartbeat information to the Signaling server at fixed intervals to keep the client active until the client actively logs out or is disconnected. The connection is interrupted when timeout occurs. During this period, users may freely access the Signaling network resources subject to their own permissions and usage restrictions.
// Paste the following code snippet below "Login to Signaling" comment
try {
// Login to Signaling
var (status,response) = await rtmClient.login(token);
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
print('login RTM success!');
}
} catch (e) {
print('Failed to login: $e');
}Send a message
To distribute a message to all subscribers of a message channel, call publish. The following code sends a string type message every second for 100 seconds.
// Paste the following code snippet below the "Publish messages" comment
// Send a message every second for 100 seconds
for (var i = 0; i < 100; i++) {
try {
var (status, response) = await rtmClient.publish(
channelName,
'message number : $i',
channelType: RtmChannelType.message,
customType: 'PlainText'
);
if (status.error == true ){
print('${status.operation} failed, errorCode: ${status.errorCode}, due to ${status.reason}');
} else {
print('${status.operation} success! message number:$i');
}
} catch (e) {
print('Failed to publish message: $e');
}
await Future.delayed(Duration(seconds: 1));
}Subscribe and unsubscribe
To receive messages sent to a channel, call subscribe to subscribe to channel messages:
// Paste the following code snippet below the "Subscribe to a channel" comment
try {
// Subscribe to a channel
var (status,response) = await rtmClient.subscribe(channelName);
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
print('subscribe channel: ${channelName} success!');
}
} catch (e) {
print('Failed to subscribe channel: $e');
}When you no longer need to receive messages from a channel, unsubscribe from the channel:
// Paste the following code snippet below the "Unsubscribe from a channel" comment
try {
// Unsubscribe from a channel
var (status,response) = await rtmClient.unsubscribe(channelName);
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
print('unsubscribe success!');
}
} catch (e) {
print('something went wrong with logout: $e');
}For more information about subscribing and sending messages, see Message channels and Stream channels.
Log out of Signaling
When a user no longer needs to use Signaling, call logout. Logging out means closing the connection between the client and Signaling. The user is automatically logged out or unsubscribed from all message and stream channels. Other users in the channel receive an onPresenceEvent notification of the user leaving the channel.
// Paste the following code snippet below the "Log out of Signaling" comment
try {
// Log out of Signaling
var (status,response) = await rtmClient.logout();
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
print('logout RTM success!');
}
} catch (e) {
print('something went wrong with logout: $e');
}Test Signaling
Take the following steps to test the sample code:
-
Use the Token Builder to generate a Signaling token:
- Select Signaling from the Agora products dropdown.
- Fill in your app ID and app certificate from Agora Console. Leave the remaining fields blank.
- Click Generate Token.
-
In your code, replace
your_appIdwith your app ID from Agora Console,your_userIdwith a numeric string andyour_tokenwith the generated token. -
Save the project.
-
Connect the target device to your computer, or open the device emulator.
-
In the terminal, execute the following command in the project path to run the sample project on the target device:
flutter runYou see the following output in the terminal:
flutter: publish success! message number:0 flutter: publish success! message number:1 flutter: publish success! message number:2 flutter: publish success! message number:3 -
Run a second instance of the project on another device or emulator using a different
userId.You see the following notifications for messages sent from the first app instance:
flutter: received a message from channel: getting-started, channel type : RtmChannelType.message flutter: message content: message number : 0, custom type: PlainText flutter: received a message from channel: getting-started, channel type : RtmChannelType.message flutter: message content: message number : 1, custom type: PlainText
Reference
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
Token authentication
In this guide you retrieve a temporary token from a Token Builder. To understand how to create an authentication server for development purposes, see Secure authentication with tokens.
Sample project
Agora provides an open source sample project on GitHub for your reference. Download it or view the source code for a more detailed example.
API reference
Understand the tech
To use Signaling features in your app, you initialize a Signaling client instance and add event listeners. To connect to Signaling, you login using an authentication token. To send a message to a message channel, you publish the message. Signaling creates a channel when a user subscribes to it. To receive messages other users publish to a channel, your app listens for events.
To create a pub/sub session for Signaling, implement the following steps in your app:
Prerequisites
To implement the code presented on this page you need to have:
- An Agora account and project.
- Enabled Signaling in Agora Console
- A device running Linux Ubuntu 14.04 or above; 18.04+ is recommended.
- At least 2 GB of memory.
cmake3.6.0 or above.
- Ensure that a firewall is not blocking your network communication.
Signaling 2.x is an enhanced version compared to 1.x with a wide range of new features. It follows a new pricing structure. See Pricing for details.
Project setup
Create a project
Create the following folder structure for your project:
RTMProject/
├── include/
└── lib/Integrate the SDK
To integrate the Linux C++ Signaling SDK into your project:
-
Download the latest version of the SDK and unzip it.
To integrate Signaling SDK 2.2.1 or later, and Video SDK 4.3.0 or later in the same project, refer to handle integration issues.
-
Copy the files in the SDK package to the project folder as follows:
- Copy the
/rtm/sdk/libagora_rtm_sdk.sofile to the project'slibfolder. - Copy all
*.hfiles in/rtm/sdk/high_level_api/includeto the project'sincludefolder.
- Copy the
-
To use
CMaketo compile the project, create the following files in theRTMProjectfolder:-
CMakeLists.txt# CMakeLists.txt cmake_minimum_required (VERSION 2.8) project(RTMProject) set(TARGET_NAME RTMQuickStart) set(SOURCES RTMQuickStart.cpp) set(HEADERS) set(TARGET_BUILD_TYPE "Debug") set(CMAKE_CXX_FLAGS "-fPIC -O2 -g -std=c++11 -msse2") include_directories(${CMAKE_SOURCE_DIR}/include) link_directories(${CMAKE_SOURCE_DIR}/lib) add_executable(${TARGET_NAME} ${SOURCES} ${HEADERS}) target_link_libraries(${TARGET_NAME} agora_rtm_sdk pthread) -
clean_build.sh# clean_build.sh rm -fr build mkdir -p build cd build cmake .. make cd ..
-
Implement Signaling
A complete code sample that implements the basic features of Signaling is presented here for your reference. To use the sample code, create a new file named RTMQuickStart.cpp in the RTMProject folder and add the following code:
Complete sample code for Signaling
#include <iostream>
#include <memory>
#include <string>
#include <exception>
//#include <unistd.h>
#include "IAgoraRtmClient.h"
// Pass in your App ID and token
const std::string APP_ID = "<Your App ID>";
const std::string TOKEN = "<Your token>";
// Terminal color codes for UBUNTU/LINUX
#define RESET "\\033\[0m"
#define BLACK "\\033\[30m" /* Black */
#define RED "\\033\[31m" /* Red */
#define GREEN "\\033\[32m" /* Green */
#define YELLOW "\\033\[33m" /* Yellow */
#define BLUE "\\033\[34m" /* Blue */
#define MAGENTA "\\033\[35m" /* Magenta */
#define CYAN "\\033\[36m" /* Cyan */
#define WHITE "\\033\[37m" /* White */
#define BOLDBLACK "\\033\[1m\\033\[30m" /* Bold Black */
#define BOLDRED "\\033\[1m\\033\[31m" /* Bold Red */
#define BOLDGREEN "\\033\[1m\\033\[32m" /* Bold Green */
#define BOLDYELLOW "\\033\[1m\\033\[33m" /* Bold Yellow */
#define BOLDBLUE "\\033\[1m\\033\[34m" /* Bold Blue */
#define BOLDMAGENTA "\\033\[1m\\033\[35m" /* Bold Magenta */
#define BOLDCYAN "\\033\[1m\\033\[36m" /* Bold Cyan */
#define BOLDWHITE "\\033\[1m\\033\[37m" /* Bold White */
using namespace agora::rtm;
class RtmEventHandler : public IRtmEventHandler {
public:
// Add the event listener
void onLoginResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
cbPrint("onLoginResult, request id: %lld, errorCode: %d", requestId, errorCode);
}
void onLogoutResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) {
cbPrint("onLogoutResult, request id: %lld, errorCode: %d", requestId, errorCode);
}
void onConnectionStateChanged(const char *channelName, RTM_CONNECTION_STATE state, RTM_CONNECTION_CHANGE_REASON reason) override {
cbPrint("onConnectionStateChanged, channelName: %s, state: %d, reason: %d", channelName, state, reason);
}
void onLinkStateEvent(const LinkStateEvent& event) override {
cbPrint("onLinkStateEvent, state: %d -> %d, operation: %d, reason: %s", event.previousState, event.currentState, event.operation, event.reason);
}
void onPublishResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
cbPrint("onPublishResult request id: %lld result: %d", requestId, errorCode);
}
void onMessageEvent(const MessageEvent &event) override {
cbPrint("receive message from: %s, message: %s", event.publisher, event.message);
}
void onSubscribeResult(const uint64_t requestId, const char *channelName, RTM_ERROR_CODE errorCode) override {
cbPrint("onSubscribeResult: channel:%s, request id: %lld result: %d", channelName, requestId, errorCode);
}
void onUnsubscribeResult(const uint64_t requestId, const char *channelName, RTM_ERROR_CODE errorCode) override {
cbPrint("onUnsubscribeResult: channel:%s, request id: %lld result: %d", channelName, requestId, errorCode);
}
private:
void cbPrint(const char* fmt, ...) {
printf("\\x1b[32m<< RTM async callback: ");
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
printf(" >>\\x1b[0m\\n");
}
};
class RtmDemo {
public:
RtmDemo()
: eventHandler_(new RtmEventHandler()),
rtmClient_(nullptr) { Init(); }
void Init() {
std::cout << YELLOW << "Please enter userID (literal \\"null\\" or starting"
<< "with space is not allowed, no more than 64 characters!):" << std::endl;
std::string userID;
std::getline(std::cin, userID);
RtmConfig config;
config.appId = APP_ID.c_str();
config.userId = userID.c_str();
config.eventHandler = eventHandler_.get();
// Create an IRtmClient instance
int errorCode = 0;
rtmClient_ = createAgoraRtmClient(config, errorCode);
if (!rtmClient_ || errorCode != 0) {
std::cout << RED <<"error creating rtm service!" << std::endl;
exit(0);
}
}
void start() {
// Log in the RTM server
uint64_t requestId = 0;
rtmClient_->login(TOKEN.c_str(), requestId);
// Sample codes for the user interface
mainMeun();
std::cout << YELLOW << "quit ? yes/no" << std::endl;
std::string input;
std::getline(std::cin, input);
if (input.compare("yes") == 0) {
exit(0);
}
}
// Log out from the RTM server
void logout() {
uint64_t requestId = 0;
rtmClient_->logout(requestId);
}
// Subscribe to a channel
void subscribeChannel(const std::string& channelName) {
uint64_t requestId = 0;
rtmClient_->subscribe(channelName.c_str(), SubscribeOptions(), requestId);
}
// Unsubscribe from a channel
void unsubscribeChannel(const std::string& channelName) {
uint64_t requestId = 0;
rtmClient_->unsubscribe(channelName.c_str(), requestId);
}
// Publish a message
void publishMessage(const std::string& channelName, const std::string& message) {
PublishOptions options;
options.messageType = RTM_MESSAGE_TYPE_STRING;
uint64_t requestId;
rtmClient_->publish(channelName.c_str(), message.c_str(), message.size(), options, requestId);
}
// Sample codes for the user interface
void mainMeun() {
bool quit = false;
while (!quit) {
std::cout << WHITE
<< "1: subscribe channel\\n"
<< "2: unsubscribe channel\\n"
<< "3: publish message\\n"
<< "4: logout" << std::endl;
std::cout << YELLOW <<"please input your choice: " << std::endl;
std::string input;
std::getline(std::cin, input);
int32_t choice = 0;
try {
choice = std::stoi(input);
} catch(...) {
std::cout <<RED << "invalid input" << std::endl;
continue;
}
switch (choice)
{
case 1: {
std::cout << YELLOW << "please input channel name:" << std::endl;
std::string channelName;
std::getline(std::cin, channelName);
subscribeChannel(channelName);
std::this_thread::sleep_for(std::chrono::seconds(1));
break;
}
case 2: {
std::cout << YELLOW << "please input channel name:" << std::endl;
std::string channelName;
std::getline(std::cin, channelName);
unsubscribeChannel(channelName);
std::this_thread::sleep_for(std::chrono::seconds(1));
break;
}
case 3: {
std::cout << YELLOW << "please input channel name:" << std::endl;
std::string channelName;
std::getline(std::cin, channelName);
groupChat(channelName);
break;
}
case 4: {
logout();
return;
}
default: {
std::cout <<RED << "invalid input" << std::endl;
break;
}
}
}
}
void groupChat(const std::string& channelName) {
std::string message;
while (true) {
std::cout << YELLOW << "please input message "
<< "or input \\"quit\\" to leave group chat"
<< std::endl;
std::getline(std::cin, message);
if (message.compare("quit") == 0) {
return;
} else {
publishMessage(channelName, message);
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
}
private:
std::unique_ptr<IRtmEventHandler> eventHandler_;
IRtmClient* rtmClient_;
};
int main(int argc, const char * argv[]) {
RtmClient messaging;
messaging.login();
return 0;
}Follow the implementation steps to understand the core API calls in the sample code or use the snippets in your own code.
Include the header file
To use Signaling APIs in your project, include the IAgoraRtmClient header file:
#include "IAgoraRtmClient.h"Initialize the Signaling engine
Before calling any other Signaling SDK API, initialize an IRtmClient object instance.
std::string userID;
std::getline(std::cin, userID);
RtmConfig config;
config.appId = APP_ID.c_str();
config.userId = userID.c_str();
config.eventHandler = eventHandler_.get();
// Create an IRtmClient instance
int errorCode = 0;
rtmClient_ = createAgoraRtmClient(config, errorCode);
if (!rtmClient_ || errorCode != 0) {
std::cout << RED <<"error creating rtm service!" << std::endl;
exit(0);
}Add an event listener
The event listener enables you to implement the processing logic in response to Signaling events. Use the following code to handle event notifications or display received messages:
void onLoginResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
cbPrint("onLoginResult, request id: %lld, errorCode: %d", requestId, errorCode);
}
void onLogoutResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) {
cbPrint("onLogoutResult, request id: %lld, errorCode: %d", requestId, errorCode);
}
void onConnectionStateChanged(const char *channelName, RTM_CONNECTION_STATE state, RTM_CONNECTION_CHANGE_REASON reason) override {
cbPrint("onConnectionStateChanged, channelName: %s, state: %d, reason: %d", channelName, state, reason);
}
void onLinkStateEvent(const LinkStateEvent& event) override {
cbPrint("onLinkStateEvent, state: %d -> %d, operation: %d, reason: %s", event.previousState, event.currentState, event.operation, event.reason);
}
void onPublishResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
cbPrint("onPublishResult request id: %lld result: %d", requestId, errorCode);
}
void onMessageEvent(const MessageEvent &event) override {
cbPrint("receive message from: %s, message: %s", event.publisher, event.message);
}
void onSubscribeResult(const uint64_t requestId, const char *channelName, RTM_ERROR_CODE errorCode) override {
cbPrint("onSubscribeResult: channel:%s, request id: %lld result: %d", channelName, requestId, errorCode);
}
void onUnsubscribeResult(const uint64_t requestId, const char *channelName, RTM_ERROR_CODE errorCode) override {
cbPrint("onUnsubscribeResult: channel:%s, request id: %lld result: %d", channelName, requestId, errorCode);
}Log in to Signaling
To connect to Signaling and access Signaling network resources, such as sending messages, and subscribing to channels, call login.
During a login operation, the client attempts to establish a connection with Signaling. Once the connection is established, the client transmits heartbeat information to the Signaling server at fixed intervals to keep the client active until the client actively logs out or is disconnected. The connection is interrupted when timeout occurs. During this period, users may freely access the Signaling network resources subject to their own permissions and usage restrictions.
// Log in to Signaling
uint64_t requestId = 0;
rtmClient_->login(TOKEN.c_str(), requestId);After you call this method, the SDK triggers the onLoginResult callback and returns the API call result.
Use the login return value, or listen to the onConnectionStateChanged event notification to confirm that login is successful or obtain the error code and cause of login failure. When performing a login operation, the client's network connection state is RTM_CONNECTION_STATE_CONNECTING. After a successful login, the state is updated to RTM_CONNECTION_STATE_CONNECTED.
Best practice
To continuously monitor the network connection state of the client, best practice is to continue to listen for onConnectionStateChanged event notifications throughout the life cycle of the application. For further details, see Event listeners.
After a user successfully logs into Signaling, the application's PCU increases, which affects your billing data.
Send a message
To distribute a message to all subscribers of a message channel, call publish. The following code sends a string type message.
// Publish a message to the channel
void publishMessage(const std::string& channelName, const std::string& message) {
PublishOptions options;
options.messageType = RTM_MESSAGE_TYPE_STRING;
uint64_t requestId;
rtmClient_->publish(channelName.c_str(), message.c_str(), message.size(), options, requestId);
}Before calling publish to send a message, serialize the message payload as a string.
Subscribe and unsubscribe
To receive messages sent to a channel, call subscribe to subscribe to channel messages:
// Subscribe to a channel
void subscribeChannel(const std::string& channelName) {
uint64_t requestId = 0;
rtmClient_->subscribe(channelName.c_str(), SubscribeOptions(), requestId);
}When you no longer need to receive messages from a channel, call unsubscribe to unsubscribe from the channel:
// Unsubscribe from a channel
void unsubscribeChannel(const std::string& channelName) {
uint64_t requestId = 0;
rtmClient_->unsubscribe(channelName.c_str(), requestId);
}For more information about sending and receiving messages see Message channels and Stream channels.
Log out of Signaling
When a user no longer needs to use Signaling, call logout. Logging out means closing the connection between the client and Signaling. The user is automatically logged out or unsubscribed from all message and stream channels. Other users in the channel receive an onPresenceEvent notification of the user leaving the channel.
// Log out of Signaling
void logout() {
uint64_t requestId = 0;
rtmClient_->logout(requestId);
}
// Clean up
rtmClient_->release();Test Signaling
Take the following steps to test the sample code:
-
Use the Token Builder to generate a Signaling token:
- Select Signaling from the Agora products dropdown.
- Fill in your app ID and app certificate from Agora Console. Leave the remaining fields blank.
- Click Generate Token.
-
In your code, replace
<Your App ID>with your app ID from Agora Console and<Your token>with the generated token. Save the project. Make sure Signaling is activated for your project in Agora Console. -
In the terminal, run the following command to compile the project:
sh +x clean_build.sh -
Use the following commands to navigate to the
buildfolder and run the app:cd build ./RTMQuickStartYou see menu options to subscribe, unsubscribe, publish, and logout.
-
Follow the prompts to subscribe to a channel.
-
Run another instance of the app using a different user ID. Follow the prompts to publish a message to the same channel that you subscribed to from the other instance.
-
You see the message displayed in the instance that you used to subscribe to the channel.
Congratulations! You have successfully integrated Signaling into your project.
Reference
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
Token authentication
In this guide you retrieve a temporary token from a Token Builder. To understand how to create an authentication server for development purposes, see Secure authentication with tokens.
Sample project
Agora provides an open source sample project on GitHub for your reference. Download it or view the source code for a more detailed example.
API reference
Understand the tech
To use Signaling features in your game, you initialize a Signaling client instance and add event listeners. To connect to Signaling, you login using an authentication token. To send a message to a message channel, you publish the message. Signaling creates a channel when a user subscribes to it. To receive messages other users publish to a channel, your game listens for events.
To create a pub/sub session for Signaling, implement the following steps in your game:
Prerequisites
To implement the code presented on this page you need to have:
-
Enabled Signaling in Agora Console
-
Operating System and compiler requirements:
Development Platform Operating system version Compiler version Android Android 4.1 or above Android Studio 3.0 or above iOS iOS 11.0 or above Xcode 9.0 or above macOS macOS 10.10 or above Xcode 9.0 or above Windows Windows 7 or above Microsoft Visual Studio 2017 or above -
Ensure that a firewall is not blocking your network communication.
Signaling 2.x is an enhanced version compared to 1.x with a wide range of new features. It follows a new pricing structure. See Pricing for details.
Project setup
Create a project
-
In Unity Hub, select Projects, then click New Project.
-
In All templates, select 3D. Set the Project name and Location, then click Create Project.
-
In Projects, double-click the project you created. Your project opens in Unity.
Integrate the SDK
-
Download the latest version of the Agora Signaling SDK, and unzip the contents to a local folder.
-
In Unity, click Assets > Import Package > Custom Package.
-
Navigate to the Signaling SDK package and click Open.
-
In Import Unity Package, click Import.
Create a user interface
To create a simple UI for verifying the basic features of Signaling, add the following elements to your interface:
SampleScene
- Main Camera
- Canvas
- Image // Window background
- Txt1 // Text label, text content is Send string message to message channel
- InputField1 // Message input box
- Btn1 // Send message
- Txt2 // Text label, text content is Send binary message to stream channel
- Btn2 // Create Stream Channel
- Btn3 // Join Stream Channel
- Btn4 // Join Topic
- Btn5 // Subscribe to message publishers in Topic
- InputField2 // Message input box
- Btn6 // Send message button
- Btn7 // Release Stream Channel button
- Btn8 // Leave Stream Channel button
- Btn9 // Leave Topic button
- Btn10 // Unsubscribe from message publishers in Topic
- Pannel // Information output panel
- Txt3 // Information output text
- EventSystemYour interface should look similar to the following:
Implement Signaling
A complete code sample that implements the basic features of Signaling is presented here for your reference. To use the sample code, create a new script for the project. In Unity editor, from the Assets menu, select Create, then C# Script. Name the new script file HelloWorld.cs and paste the following code in the file:
Complete sample code for Signaling
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Agora.Rtm;
public class HelloWorld : MonoBehaviour
{
[SerializeField]
private string appId = "your_appId"; // Get an App ID from Agora console
private string userId = "your_userId"; // Change the user ID to a numeric string
private string token = "your_token"; // Login token
private string rtcToken = "your_RTC_token"; // Token to join a stream channel
private string msChannelName = "SeeYou";
private string stChannelName = "Greeting";
private string topicName = "Hello_world";
private IRtmClient rtmClient;
private IStreamChannel streamChannel;
public void Awake()
{
Initialize();
}
// Initialize RTM
public async void Initialize()
{
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(appId))
{
ShowMessage("We need a userId and appId to initialize!");
return;
}
RtmLogConfig logConfig = new RtmLogConfig();
// Set log file path
logConfig.filePath = "myFilePath";
// Set the file size of the agore.log file
logConfig.fileSizeInKB = 512;
// Set the log report level
logConfig.level = RTM_LOG_LEVEL.INFO;
RtmConfig config = new RtmConfig();
config.appId = appId;
config.userId = userId;
config.logConfig = logConfig;
// Create the RTM Client
try
{
rtmClient = RtmClient.CreateAgoraRtmClient(config);
ShowMessage("RTM Client Initialize Sucessfull");
// Inactive unnecessary components
string[] activeComponentsList = { "Btn2" };
string[] inactiveComponentList = { "Btn3", "Btn4", "Btn5", "Btn6", "Btn7", "Btn8", "Btn9", "Btn10" };
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
catch (RTMException e)
{
ShowMessage(string.Format("{0} is failed", e.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", e.Status.ErrorCode, e.Status.Reason));
}
// Add listener
if (rtmClient != null)
{
// Add the message event listener
rtmClient.OnMessageEvent += OnMessageEvent;
// Add the presence event listener
rtmClient.OnPresenceEvent += OnPresenceEvent;
// Add the connection state change listener
rtmClient.OnConnectionStateChanged += OnConnectionStateChanged;
// Log in to the RTM server
var result = await rtmClient.LoginAsync(token);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
Debug.Log(string.Format("Login failed"));
}
else
{
ShowMessage("Login Successfully");
}
//Subscribe to a Message Channel
SubscribeOptions options = new SubscribeOptions()
{
withMessage = true,
withPresence = true
};
var result2 = await rtmClient.SubscribeAsync(msChannelName, options);
var status2 = result2.Status;
var response2 = result2.Response;
if (status2.Error)
{
ShowMessage(string.Format("{0} is failed", status2.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status2.ErrorCode, status2.Reason));
}
else
{
ShowMessage(string.Format("Subscribe channel success! at Channel:{0}", response2.ChannelName));
}
}
}
private void ChangeComponentView(string[] componentsList, string viewType)
{
for (int i = 0; i < componentsList.Length; i++)
{
if (viewType == "ACTIVATE")
GameObject.Find(componentsList[i]).GetComponent<Button>().interactable = true;
else if (viewType == "INACTIVATE")
GameObject.Find(componentsList[i]).GetComponent<Button>().interactable = false;
}
}
private void ShowMessage(string msg)
{
GameObject.Find("Txt3").GetComponent<Text>().text += "
" + msg;
}
// Implement the event listener handler
// Implement the connection state change event handler
private void OnConnectionStateChanged(string channelName, RTM_CONNECTION_STATE state, RTM_CONNECTION_CHANGE_REASON reason)
{
ShowMessage(string.Format("Channel:{0} connection state have changed to:{1} because of {2}", channelName, state, reason));
}
// Implement the presence event handler
private void OnPresenceEvent(PresenceEvent eve)
{
var channelName = eve.channelName;
var channelType = eve.channelType;
var eventType = eve.type;
var publisher = eve.publisher;
var stateItems = eve.stateItems;
var interval = eve.interval;
var snapshot = eve.snapshot;
Debug.Log(string.Format("The Event : {0} Happend", eventType));
switch (eventType)
{
case RTM_PRESENCE_EVENT_TYPE.SNAPSHOT:
ShowMessage(string.Format("You have sub or join channel:{0} channe type is:{1}", channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.INTERVAL:
ShowMessage(string.Format("The channel:{0} channe type is:{1} is now in interval mode!", channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_JOIN:
ShowMessage(string.Format("User:{0} sub or join channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_LEAVE:
ShowMessage(string.Format("User:{0} unsub or leave channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_TIMEOUT:
ShowMessage(string.Format("User:{0} timeout from channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_STATE_CHANGED:
ShowMessage(string.Format("User:{0} state change in channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.ERROR_OUT_OF_SERVICE:
ShowMessage(string.Format("User:{0} joined channel without presence service:{1} channe type is:{2}", publisher, channelName, channelType));
break;
}
}
// Implement the message event handler
private void OnMessageEvent(MessageEvent eve)
{
var channelName = eve.channelName;
var channelType = eve.channelType;
var topic = eve.channelTopic;
var publisher = eve.publisher;
var messageType = eve.messageType;
var customType = eve.customType;
var message = eve.message;
if (messageType == RTM_MESSAGE_TYPE.STRING)
{
var stMessage = message.GetData<string>();
ShowMessage(string.Format("You have recieved a string type message: {0} from: {1} in channel:{2}", stMessage, publisher, channelName));
ShowMessage(string.Format("The channel type is {0}", channelType));
}
else
{
var biMessage = message.GetData<byte[]>();
ShowMessage(string.Format("You have recieved a binary type message: {0},from: {1} in channel:{2}", System.BitConverter.ToString(biMessage), publisher, channelName));
ShowMessage(string.Format("The channel type is {0}", channelType));
}
}
// OnClick Event Handler for Btn1
public async void PublishStringMessage()
{
// Publish string messages to a message channel
var message = GameObject.Find("InputField1").GetComponent<InputField>().text;
if (rtmClient != null)
{
PublishOptions options = new PublishOptions()
{
channelType = RTM_CHANNEL_TYPE.MESSAGE,
customType = "PlainText"
};
var result = await rtmClient.PublishAsync(msChannelName, message, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed, The error code is {1}", status.Operation, status.ErrorCode));
}
else
{
ShowMessage("Publish Message Success!");
}
}
}
// OnClick Event Handler for Btn6
public async void PublishBinaryMessage()
{
// Publish Binary Messahe to Stream Channel
var message = GameObject.Find("InputField2").GetComponent<InputField>().text;
byte[] byteMsg = System.Text.Encoding.Default.GetBytes(message);
if (streamChannel != null)
{
TopicMessageOptions options = new TopicMessageOptions();
options.customType = "PlainBinary";
options.sendTs = 0;
var result = await streamChannel.PublishTopicMessageAsync(topicName, byteMsg, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage("Publish Binary Message Success!");
}
}
}
// Onclick event handler for Btn2
public void CreateStChannel()
{
string[] activeComponentsList = { "Btn3", "Btn7" };
string[] inactiveComponentList = { "Btn2", "Btn4", "Btn5", "Btn6", "Btn8", "Btn9", "Btn10" };
// Create a Stream Channel
if (rtmClient != null)
{
streamChannel = rtmClient.CreateStreamChannel(stChannelName);
ShowMessage("Create Stream Channel:" + stChannelName + "Success !");
// Disable unnecessary components
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
// Onclick event handler for Btn3
public async void JoinStChannel()
{
string[] activeComponentsList = { "Btn4", "Btn5", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn6", "Btn9", "Btn10", "Btn7" };
// Join a Stream Channel
if (streamChannel != null)
{
JoinChannelOptions options = new JoinChannelOptions();
options.withPresence = true;
options.token = rtcToken;
var result = await streamChannel.JoinAsync(options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Join stream channel success! at Channel:{1}", response.UserId, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
}
// Onclick event handler for Btn4
public async void JoinTopic()
{
string[] activeComponentsList = { "Btn6", "Btn8", "Btn9" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn4", "Btn7" };
// Join a topic
if (streamChannel != null)
{
JoinTopicOptions options = new JoinTopicOptions();
options.qos = RTM_MESSAGE_QOS.ORDERED;
options.syncWithMedia = false;
var result = await streamChannel.JoinTopicAsync(topicName, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Join Topic:{1} success! at Channel:{2}", response.UserId, response.Topic, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
}
// Onclick event handler for Btn5
public async void SubscribeTopic()
{
string[] activeComponentsList = { "Btn8", "Btn10" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn5", "Btn7" };
// Subscribe a publisher from a topic
List<string> userList = new List<string>();
userList.Add("Tony");
TopicOptions options = new TopicOptions();
options.users = userList.ToArray();
var result = await streamChannel.SubscribeTopicAsync(topicName, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Subscribe Topic:{1} success! at Channel:{2}", response.UserId, response.Topic, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
// Onclick event handler for Btn7
public async void ReleaseStChannel()
{
string[] activeComponentsList = { "Btn2" };
string[] inactiveComponentList = { "Btn3", "Btn4", "Btn5", "Btn6", "Btn7", "Btn8", "Btn9", "Btn10" };
// Release a Stream Channel
if (rtmClient != null && streamChannel != null)
{
var result = await streamChannel.LeaveAsync();
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage("Dispose Channel Success!");
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
var status1 = streamChannel.Dispose();
}
}
// Onclick event handler for Btn8
public async void LeaveStChannel()
{
string[] activeComponentsList = { "Btn3", "Btn7" };
string[] inactiveComponentList = { "Btn2", "Btn4", "Btn5", "Btn6", "Btn8", "Btn9", "Btn10" };
// Leave a Stream Channel
if (streamChannel != null)
{
var result = await streamChannel.LeaveAsync();
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Leave stream channel success! at Channel:{1}", response.UserId, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
}
// Onclick event handler for Btn9
public async void LeaveTopic()
{
string[] activeComponentsList = { "Btn4", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn6", "Btn7", "Btn9" };
// Leave a topic
if (streamChannel != null)
{
var result = await streamChannel.LeaveTopicAsync(topicName);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Leave Topic:{1} success! at Channel:{2}", response.UserId, response.Topic, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
}
// Onclick event handler for Btn10
public async void UnsubscribeTopic()
{
string[] activeComponentsList = { "Btn5", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn7", "Btn10" };
// Unsubscribe a publisher from a topic
List<string> userList = new List<string>();
userList.Add("Tony");
TopicOptions options = new TopicOptions();
options.users = userList.ToArray();
var result = await streamChannel.UnsubscribeTopicAsync(topicName, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("Unsubscribe Topic Success!"));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
private async void OnDestroy()
{
string[] activeComponentsList = { "Btn4", "Btn5", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn6", "Btn9", "Btn10", "Btn7" };
// Dispose a Stream Channel
if (rtmClient != null && streamChannel != null)
{
var result = await streamChannel.LeaveAsync();
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage("Dispose Channel Success!");
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
var status1 = streamChannel.Dispose();
}
if (rtmClient != null)
{
var result = await rtmClient.UnsubscribeAsync(msChannelName);
var status2 = result.Status;
var response2 = result.Response;
if (status2.Error)
{
Debug.Log(string.Format("{0} is failed", status2.Operation));
Debug.Log(string.Format("The error code is {0}, because of: {1}", status2.ErrorCode, status2.Reason));
}
else
{
Debug.Log("Unsubscribe Channel Success!");
}
var status3 = rtmClient.Dispose();
Debug.Log("Dispose rtmClient Success!");
rtmClient = null;
}
}
}Follow the implementation steps to understand the core API calls in the sample code or use the snippets in your own code.
Import Agora classes
To use the relevant Unity and Signaling APIs in your project, import the corresponding namespaces:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Agora.Rtm;Initialize the Signaling engine
Before calling any other Signaling SDK API, initialize an IRtmClient instance.
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(appId))
{
ShowMessage("We need a userId and appId to initialize!");
return;
}
RtmLogConfig logConfig = new RtmLogConfig();
// Set log file path
logConfig.filePath = "myFilePath";
// Set the file size of the agore.log file
logConfig.fileSizeInKB = 512;
// Set the log report level
logConfig.level = RTM_LOG_LEVEL.INFO;
RtmConfig config = new RtmConfig();
config.appId = appId;
config.userId = userId;
config.logConfig = logConfig;
// Create the RTM Client
try
{
rtmClient = RtmClient.CreateAgoraRtmClient(config);
ShowMessage("RTM Client Initialize Sucessfull");
// Inactive unnecessary components
string[] activeComponentsList = { "Btn2" };
string[] inactiveComponentList = { "Btn3", "Btn4", "Btn5", "Btn6", "Btn7", "Btn8", "Btn9", "Btn10" };
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
catch (RTMException e)
{
ShowMessage(string.Format("{0} is failed", e.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", e.Status.ErrorCode, e.Status.Reason));
}Add an event listener
The event listener enables you to implement the processing logic in response to Signaling events. Use the following code to handle event notifications or display received messages:
private void OnConnectionStateChanged(string channelName, RTM_CONNECTION_STATE state, RTM_CONNECTION_CHANGE_REASON reason)
{
ShowMessage(string.Format("Channel:{0} connection state have changed to:{1} because of {2}", channelName, state, reason));
}
// Implement the presence event handler
private void OnPresenceEvent(PresenceEvent eve)
{
var channelName = eve.channelName;
var channelType = eve.channelType;
var eventType = eve.type;
var publisher = eve.publisher;
var stateItems = eve.stateItems;
var interval = eve.interval;
var snapshot = eve.snapshot;
Debug.Log(string.Format("The Event : {0} Happend", eventType));
switch (eventType)
{
case RTM_PRESENCE_EVENT_TYPE.SNAPSHOT:
ShowMessage(string.Format("You have sub or join channel:{0} channe type is:{1}", channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.INTERVAL:
ShowMessage(string.Format("The channel:{0} channe type is:{1} is now in interval mode!", channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_JOIN:
ShowMessage(string.Format("User:{0} sub or join channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_LEAVE:
ShowMessage(string.Format("User:{0} unsub or leave channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_TIMEOUT:
ShowMessage(string.Format("User:{0} timeout from channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_STATE_CHANGED:
ShowMessage(string.Format("User:{0} state change in channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.ERROR_OUT_OF_SERVICE:
ShowMessage(string.Format("User:{0} joined channel without presence service:{1} channe type is:{2}", publisher, channelName, channelType));
break;
}
}
// Implement the message event handler
private void OnMessageEvent(MessageEvent eve)
{
var channelName = eve.channelName;
var channelType = eve.channelType;
var topic = eve.channelTopic;
var publisher = eve.publisher;
var messageType = eve.messageType;
var customType = eve.customType;
var message = eve.message;
if (messageType == RTM_MESSAGE_TYPE.STRING)
{
var stMessage = message.GetData<string>();
ShowMessage(string.Format("You have recieved a string type message: {0} from: {1} in channel:{2}", stMessage, publisher, channelName));
ShowMessage(string.Format("The channel type is {0}", channelType));
}
else
{
var biMessage = message.GetData<byte[]>();
ShowMessage(string.Format("You have recieved a binary type message: {0},from: {1} in channel:{2}", System.BitConverter.ToString(biMessage), publisher, channelName));
ShowMessage(string.Format("The channel type is {0}", channelType));
}
}Log in to Signaling
To connect to Signaling and access Signaling network resources, such as sending messages, and subscribing to channels, call login.
During a login operation, the client attempts to establish a connection with Signaling. Once the connection is established, the client transmits heartbeat information to the Signaling server at fixed intervals to keep the client active until the client actively logs out or is disconnected. The connection is interrupted when timeout occurs. During this period, users may freely access the Signaling network resources subject to their own permissions and usage restrictions.
// Log in to Signaling
var result = await rtmClient.LoginAsync(token);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage("Login Successfully");
}Use the LoginAsync return value, or listen to the OnConnectionStateChanged event notification to confirm that login is successful or obtain the error code and cause of login failure. When performing a login operation, the client's network connection state is CONNECTING. After a successful login, the state is updated to CONNECTED.
Best practice
To continuously monitor the network connection state of the client, best practice is to continue to listen for OnConnectionStateChanged event notifications throughout the life cycle of the application. For further details, see Event listeners.
After a user successfully logs into Signaling, the application's PCU increases, which affects your billing data.
Send a message
To distribute a message to all subscribers of a message channel, call PublishAsync. The following code sends a string type message.
// Send a message to a channel
var message = GameObject.Find("InputField1").GetComponent<InputField>().text;
if (rtmClient != null)
{
PublishOptions options = new PublishOptions()
{
channelType = RTM_CHANNEL_TYPE.MESSAGE,
customType = "PlainText"
};
var result = await rtmClient.PublishAsync(msChannelName, message, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed, The error code is {1}", status.Operation, status.ErrorCode));
}
else
{
ShowMessage("Publish Message Success!");
}
}Before calling PublishAsync to send a message, serialize the message payload as a string.
Subscribe and unsubscribe
To receive messages sent to a channel, call SubscribeAsync to subscribe to channel messages:
//Subscribe to a Message Channel
SubscribeOptions options = new SubscribeOptions()
{
withMessage = true,
withPresence = true
};
var result2 = await rtmClient.SubscribeAsync(msChannelName, options);
var status2 = result2.Status;
var response2 = result2.Response;
if (status2.Error)
{
ShowMessage(string.Format("{0} is failed", status2.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status2.ErrorCode, status2.Reason));
}
else
{
ShowMessage(string.Format("Subscribe channel success! at Channel:{0}", response2.ChannelName));
}When you no longer need to receive messages from a channel, call UnsubscribeAsync to unsubscribe from the channel:
if (rtmClient != null)
{
var result = await rtmClient.UnsubscribeAsync(msChannelName);
var status2 = result.Status;
var response2 = result.Response;
if (status2.Error)
{
Debug.Log(string.Format("{0} is failed", status2.Operation));
Debug.Log(string.Format("The error code is {0}, because of: {1}", status2.ErrorCode, status2.Reason));
}
else
{
Debug.Log("Unsubscribe Channel Success!");
}
var status3 = rtmClient.Dispose();
Debug.Log("Dispose rtmClient Success!");
rtmClient = null;
}For more information about sending and receiving messages see Message channels and Stream channels.
Log out of Signaling
When a user no longer needs to use Signaling, call LogoutAsync. Logging out means closing the connection between the client and Signaling. The user is automatically logged out or unsubscribed from all message and stream channels. Other users in the channel receive an OnPresenceEvent notification of the user leaving the channel.
// Logout of Signaling
var (status,response) = await rtmClient.LogoutAsync();Test Signaling
Take the following steps to test the sample code:
-
In your code, replace
your_appIdwith your app ID from Agora Console andyour_userIdwith a numeric string. -
Use the Token Builder to generate a Signaling token:
- Select Signaling from the Agora products dropdown.
- Fill in your app ID and app certificate from Agora Console. Leave the remaining fields blank.
- Click Generate Token.
- In your code, replace
your_tokenwith the generated token.
-
Repeat the previous step to generate a stream channel token but this time also fill in the channel name with the value for
stChannelNameand the User ID with the value foruserIdfrom your code. Use the generated token to replaceyour_RTC_tokenin your code. -
In the Unity Editor, mount the event handling functions defined in the code to the corresponding Button components.
-
In Unity, click Play. You see the game running on your device.
-
Run another instance of the game with the same
appIdbut a differentuserIdand tokens. -
In either game instance, type a message in the input box under Send string message to message channel and press Send Message.
You see the message displayed in the other game instance.
Congratulations! You have successfully integrated Signaling into your project.
Reference
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
Token authentication
In this guide you retrieve a temporary token from a Token Builder. To understand how to create an authentication server for development purposes, see Secure authentication with tokens.
