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:

  • An Agora account and project.

  • 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

  1. Download the latest version of Signaling SDK.

  2. Copy the files in the SDK package folder /libs/AgoraRtmKit.xcframework to the project path.

  3. Open Xcode and navigate to the TARGETS > Project Name > General > Frameworks, Libraries, and Embedded Content menu.

  4. 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

  1. To follow this procedure, ensure that you have Cocoapods installed. To install Cocoapods, refer to Getting Started with CocoaPods.

  2. In the terminal, go to the project root directory and run the pod init command. A text file named Podfile is generated in the project folder .

  3. Open the Podfile and modify the content as follows:

    platform :ios, '11.0'
         target 'Your App' do
         pod 'AgoraRtm', 'x.y.z'
       end

    Replace Your App with your target name and x.y.z with 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 to AgoraRtm_iOS.

  4. Run the pod install command in the terminal to install the Signaling SDK. You see the message "Pod installation complete!".

  5. After successful installation, a file with the .xcworkspace suffix 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()
    }
}

Implement 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()
    }
}

Follow 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] = []

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)
    }
}

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!)")
}

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.")
        }
    })
}

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 = ""
}

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.")
        }
    }
}

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.")
        }
    }
}

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!")
}

Test Signaling

Take the following steps to test the sample code:

  1. Use the Token Builder to generate a Signaling token:

    1. Select Signaling from the Agora products dropdown.
    2. Fill in your app ID and app certificate from Agora Console. Leave the remaining fields blank.
    3. Click Generate Token.
  2. In your code, replace the values for appID and token with your app ID and the generated token.

  3. Choose the device or simulator you want to run the app on from the device selection dropdown menu in the Xcode toolbar.

  4. 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.

  5. Use the device as the receiving end and perform the following operations:

    1. Enter your User ID and click Login.
    2. Enter the Channel name and click Subscribe .
  6. On a second device, repeat the previous steps to install and launch the app. Use this device as the sending end.

    1. Enter a different User ID and click Login.
    2. Enter the same Channel name.
    3. Type a message and click Publish message.
  7. 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