Embed a custom plugin

Updated

Develop a custom plugin, such as a countdown plugin or a dice, and embed the plugin in the flexible classroom.

In online classroom applications, some customization is often necessary to meet the needs of a particular use case. Agora provides Widgets to help users develop plugins according to their specific needs and embed them into Flexible Classroom.

iOS

Implement a custom plugin

This section uses the countdown plugin as an example to introduce the basic steps of implementing a custom plugin through a widget and embedding the plugin in Flexible Classroom.

Implement the widget

Refer to the following steps to implement a custom widget:

  1. Import the AgoraWidget library:

    import AgoraWidget
  2. Initialize the widget's interface and data in the onWidgetDidLoad method:

    public override func onWidgetDidLoad() {
        super.onWidgetDidLoad()
        initViews()
        initConstraints()
        updateRoomData()
        updateViewData()
        updateViewFrame()
    
        log(
          content: info.roomProperties?.jsonString() ?? "nil",
          extra: nil,
          type: .info)
    }
  3. Monitor onWidgetRoomPropertiesUpdated and onMessageReceived methods to update data:

    public override func onWidgetRoomPropertiesUpdated(
                        _ properties: [String : Any],
                        cause: [String : Any]?,
                        keyPaths: [String]) {
        super.onWidgetRoomPropertiesUpdated(
          properties,
          cause: cause,
          keyPaths: keyPaths)
        updateRoomData()
        updateViewData()
        shouldStartTime()
    
        log(
          content: properties.jsonString() ?? "nil",
          extra: cause?.jsonString(),
          type: .info)
    }
    
    public override func onMessageReceived(_ message: String) {
        super.onMessageReceived(message)
    
        if let timestamp = message.toSyncTimestamp() {
            objectCreateTimestamp = timestamp
            initCurrentTimestamp()
            shouldStartTime()
        }
    
        log(content: message, type: .info)
    }
  4. If you need to change the size of the widget, send a message using sendMessage; then the parent container that listens to the message updates the size of the widget:

    func updateViewFrame() {
        let size = ["width": countdownView.neededSize.width, "height": countdownView.neededSize.height]
    
        guard let message = ["size": size].jsonString() else {
            return
        }
    
        sendMessage(message)
    }

Register Widget in Agora Classroom SDK

Add a countdown widget to AgoraEduLaunchConfig.widgets using AgoraWidgetConfig, and register the implemented plugin to the Agora Classroom SDK during launch:

let launchConfig = AgoraEduLaunchConfig(
  userName: userName,
  userUuid: userUuid,
  userRole: userRole,
  roomName: roomName,
  roomUuid: roomUuid,
  roomType: roomType,
  appId: appId,
  token: token,
  startTime: nil,
  duration: nil,
  region: region,
  mediaOptions: mediaOptions,
  userProperties: nil)

let widgetId = "countdownTimer"

launchConfig.widgets[widgetId] = AgoraWidgetConfig(with: AgoraCountdownTimerWidget.self, widgetId: widgetId)

AgoraClassroomSDK.launch(
  launchConfig,
  success: successBlock,
  failure: failureBlock)

Using Widgets in the Classroom

Refer to the following steps to integrate the countdown plugin in the classroom:

  1. Add an observer of the widget activity through AgoraEduContext.widget. Use the addWidgetActivityObserver method to listen for the callback of the activity using AgoraClassToolsViewController:

    override func viewDidLoad() {
        super.viewDidLoad()
        contextPool.room.registerRoomEventHandler(self)
        contextPool.widget.add(self)
    }
  2. Listen to the activity's callback and create or destroy the widget:

    extension AgoraClassToolsViewController: AgoraWidgetActivityObserver {
        // Create a widget when an `onWidgetActive` callback is received
        func onWidgetActive(_ widgetId: String) {
            createWidget(widgetId)
        }
    
        // When the `onWidgetInactive` callback is received, destroy the widget
        func onWidgetInactive(_ widgetId: String) {
            releaseWidget(widgetId)
        }
    }
  3. Create the Widget object of AgoraCountdownTimer and implement the communication between the widget and the local client:

    // Call the createWidget method to create the Widget object of AgoraCountdownTimer
    func createWidget(_ widgetId: String) {
        let widgetController = contextPool.widget
    
        guard widgetIdList.contains(widgetId),
            // Call getWidgetConfig to get AgoraWidgetConfig of AgoraCountdownTimer
            let config = widgetController.getWidgetConfig(widgetId) else {
                return
        }
    
        if let _ = getWidget(widgetId) {
            return
        }
    
        // Add AgoraClassToolsViewController as the observer of widget syncFrame through addObserverForWidgetSyncFrame of AgoraWidgetContext, and monitor the callback of syncFrame
        widgetController.addObserver(forWidgetSyncFrame: self, widgetId: widgetId)
    
        // Add AgoraClassToolsViewController as an observer of widget message through addWidgetMessageObserver of AgoraWidgetContext, and monitor the callback of message
        widgetController.add(self, widgetId: widgetId)
    
        let widget = widgetController.create(config)
    
        view.addSubview(widget.view)
    
        switch widgetId {
        case PollWidgetId:
          pollWidget = widget
        case CountdownTimerWidgetId:
          countdownTimerWidget = widget
        case PopupQuizWidgetId:
          popupQuizWidget = widget
        default:
          return
        }
    
        sendWidgetCurrentTimestamp(widgetId)
    }
    
    func sendWidgetCurrentTimestamp(_ widgetId: String) {
        let syncTimestamp = contextPool.monitor.getSyncTimestamp()
        let tsDic = ["syncTimestamp": syncTimestamp]
    
        if let string = tsDic.jsonString() {
            // Call the sendMessage method to send the current timestamp to the widget
            contextPool.widget.sendMessage(toWidget: widgetId, message: string)
        }
    }
  4. Implement the syncFrame callback

    extension AgoraClassToolsViewController: AgoraWidgetSyncFrameObserver {
        // When syncFrame is updated, update the position and size of the widget
        func onWidgetSyncFrameUpdated(
            _ syncFrame: CGRect,
            widgetId: String,
            operatorUser: AgoraWidgetUserInfo?) {
                let size = getWidgetSize(widgetId)
                updateWidgetFrame(widgetId, size: size)
        }
    }
  5. Implement a callback method for listening to messages.

    extension AgoraClassToolsViewController: AgoraWidgetMessageObserver {
        // When the message callback is received, AgoraClassToolsViewController acts as the parent container to update the size of the widget
        func onMessageReceived(_ message: String, widgetId: String) {
            if let size = parseSizeMessage(widgetId: widgetId, message: message) {
                updateWidgetFrame(widgetId, size: size)
            }
        }
    }

API reference

AgoraBaseWidget

AgoraBaseWidget is the base class of a Widget. Widgets that implement specific functions need to inherit from this class.

Members

ParameterTypeDescription
infoAgoraWidgetInfoThe basic information required by the widget is assigned by the widget controller.
viewViewWidget's container view.

updateWidgetRoomProperties

Update the widget's room properties. The updated room properties pass to the Controller through the onWidgetRoomPropertiesUpdated callback.

void updateWidgetRoomProperties(
    Map<String: Any> properties,
    Map<String: Any> cause,
    Callback<Void> success,
    Callback<Error> failure)

Parameters

ParameterTypeDescription
propertiesMap<String: Any>Properties to update, supports keyPath.
causeMap<String: Any>The reason for the update, can be empty.
successCallback<Void>Request succeeded.
failureCallback<Error>The request failed, returning an error.

deleteWidgetRoomProperties

Delete the widget's room property. The deleted room property passes to the Controller through the onWidgetRoomPropertiesDeleted callback.

void deleteWidgetRoomProperties(
    Array<String> keyPaths,
    Map<String: Any> cause,
    Callback<Void> success,
    Callback<Error> failure)

Parameters

ParameterTypeDescription
keyPathsArray<String>The key array of properties to delete, supports keyPath.
causeMap<String: Any>The reason for the deletion, can be empty.
successCallback<Void>Request succeeded.
failureCallback<Error>The request failed, returning an error.

updateWidgetUserProperties

Update the widget's user properties. The updated room properties pass to the Controller through the onWidgetUserPropertiesUpdated callback.

void updateWidgetUserProperties(
    Map<String: Any> properties,
    Map<String: Any> cause,
    Callback<Void> success,
    Callback<Error> failure)

Parameters

ParameterTypeDescription
propertiesMap<String: Any>Properties to update, supports keyPath. Only need to pass in the value to update.
causeMap<String: Any>The reason for the update, can be empty.
successCallback<Void>Request succeeded.
failureCallback<Error>The request failed, returning an error.

deleteWidgetUserProperties

Delete the user properties of the widget. The deleted room property passes to the Controller through the onWidgetUserPropertiesDeleted callback.

void deleteWidgetUserProperties(
    Array<String> keyPaths,
    Map<String: Any> cause,
    Callback<Void> success,
    Callback<Error> failure)

Parameters

ParameterTypeDescription
keyPathsArray<String>The key array of properties to delete, supports keyPath.
causeMap<String: Any>The reason for the update, can be empty.
successCallback<Void>Request succeeded.
failureCallback<Error>The request failed, returning an error.

sendMessage

Send a message to the observer.

void sendMessage(String message)

Parameters

ParameterTypeDescription
messageStringMessage content.

onLoad

The widget loading is complete; it is loaded inside the Controller.

void onLoad()

onMessageReceived

The Controller notifies the widget of received messages.

The sendMessageToWidget callback is fired after calling in the WidgetContext protocol.

void onMessageReceived(String message)

Parameters

ParameterTypeDescription
messageStringMessage content.

onLocalUserInfoUpdated

The Controller notifies the widget of local user information updates.

void onLocalUserInfoUpdated(AgoraWidgetUserInfo localUserInfo)

Parameters

ParameterTypeDescription
localUserInfoAgoraWidgetUserInfoLocal user information.

onRoomInfoUpdated

The Controller notifies the widget of room information updates.

void onRoomInfoUpdated(AgoraWidgetRoomInfo roomInfo)

Parameters

ParameterTypeDescription
roomInfoAgoraWidgetRoomInfoRoom information.

onWidgetRoomPropertiesUpdated

The Controller notifies the widget of room property updates.

void onWidgetRoomPropertiesUpdated(
    Map<String: Any> properties,
    Map<String: Any> cause,
    Array<String> keyPaths,
    AgoraWidgetUserInfo operatorUser)

Parameters

ParameterTypeDescription
propertiesMap<String: Any>Final complete properties.
causeMap<String: Any>Reason, can be empty.
keyPathsArray<String>Array of keys for properties that changed.
operatorUserAgoraWidgetUserInfoOperator, can be empty.

onWidgetRoomPropertiesDeleted

Controller notifies the widget of room attribute deletion.

void onWidgetRoomPropertiesDeleted(
    Map<String: Any> properties,
    Map<String: Any> cause,
    Array<String> keyPaths,
    AgoraWidgetUserInfo operatorUser)

Parameters

ParameterTypeDescription
propertiesMap<String: Any>Final complete properties.
causeMap<String: Any>Reason, can be empty.
keyPathsArray<String>Array of keys for properties to be deleted.
operatorUserAgoraWidgetUserInfoOperator, can be empty.

onWidgetUserPropertiesUpdated

The Controller notifies the widget of user attribute updates.

void onWidgetUserPropertiesUpdated(
    Map<String: Any> properties,
    Map<String: Any> cause,
    Array<String> keyPaths,
    AgoraWidgetUserInfo operatorUser)

Parameters

ParameterTypeDescription
propertiesMap<String: Any>Final complete properties.
causeMap<String: Any>Reason, can be empty.
keyPathsArray<String>Array of keys for properties that changed.
operatorUserAgoraWidgetUserInfoOperator, can be empty.

onWidgetUserPropertiesDeleted

The Controller notifies the widget of user attribute deletions.

void onWidgetUserPropertiesDeleted(
    Map<String: Any> properties,
    Map<String: Any> cause,
    Array<String> keys)

Parameters

ParameterTypeDescription
propertiesMap<String: Any>Final complete properties.
causeMap<String: Any>Reason, can be empty.
keysArray<String>Array of keys for properties to delete.