Skip to main content

Embed a custom plugin

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

Widgets are stand-alone plugins that contain interface and functionality. Developers can implement a widget based on customization of the base class, and then register the widget in the Agora Classroom SDK. Agora Classroom SDK supports registering multiple widgets. Widgets can communicate with other widgets, as well as with other plugins in the UI layer.

Implement a custom plugin

This section uses the cloud disk plug-in as an example to introduce the basic steps of implementing a custom plug-in through Widget, and embedding the plug-in in Flexible Classroom. The complete code can be viewed in the CloudClass-Android repository.

  1. Define the class


    _29
    class FCRCloudDiskWidget : AgoraBaseWidget() {
    _29
    override val tag = "AgoraEduNetworkDiskWidget"
    _29
    _29
    private var cloudDiskContent: AgoraEduCloudDiskWidgetContent? = null
    _29
    _29
    // Initialize Widget
    _29
    // Add your custom UI layout to the container here
    _29
    override fun init(container: ViewGroup) {
    _29
    super.init(container)
    _29
    container.post {
    _29
    widgetInfo?.let {
    _29
    // Instantiate content and add CloudDisk view to container
    _29
    cloudDiskContent = AgoraEduCloudDiskWidgetContent(container, it)
    _29
    }
    _29
    }
    _29
    }
    _29
    _29
    // Release used resources
    _29
    override fun release() {
    _29
    cloudDiskContent?.dispose()
    _29
    super.release()
    _29
    }
    _29
    _29
    // The content class is only for encapsulation of the code.
    _29
    // Functional details are placed in the content.
    _29
    private inner class AgoraEduCloudDiskWidgetContent(val container: ViewGroup, val widgetInfo: AgoraWidgetInfo) {
    _29
    ......
    _29
    }
    _29
    }

    The complete code can be viewed in the CloudClass-Android repository.

  2. Configure the plugin in to register it with the Agora Classroom SDK


    _21
    private fun configPublicCourseware(launchConfig: AgoraEduLaunchConfig) {
    _21
    val courseware0 = CoursewareUtil.transfer(DefaultPublicCoursewareJson.data0)
    _21
    val courseware1 = CoursewareUtil.transfer(DefaultPublicCoursewareJson.data1)
    _21
    val publicCoursewares = ArrayList<AgoraEduCourseware>(2)
    _21
    publicCoursewares.add(courseware0)
    _21
    publicCoursewares.add(courseware1)
    _21
    _21
    // custom data for map structure
    _21
    val cloudDiskExtra = mutableMapOf<String, Any>()
    _21
    cloudDiskExtra[publicResourceKey] = publicCoursewares
    _21
    cloudDiskExtra[configKey] = Pair(launchConfig.appId, launchConfig.userUuid)
    _21
    val widgetConfigs = mutableListOf<AgoraWidgetConfig>()
    _21
    widgetConfigs.add(
    _21
    // Instantiate widgetConfig and add it to widgetConfig collection
    _21
    AgoraWidgetConfig(widgetClass = FCRCloudDiskWidget::class.java, widgetId = AgoraWidgetDefaultId.AgoraCloudDisk.id,
    _21
    extraInfo = cloudDiskExtra)
    _21
    )
    _21
    // Copy widgetConfigs into the SDK's startup parameters.
    _21
    // After calling launch, Smart Class will pass widgetConfigs to internal to complete the registration.
    _21
    launchConfig.widgetConfigs = widgetConfigs
    _21
    }

  3. Instantiate and initialize the widget


    _90
    // Add an AgoraWidgetActiveObserver through widgetContext.addWidgetActiveObserver
    _90
    // to monitor the activation and deregistration of the Widget
    _90
    private val widgetActiveObserver = object : AgoraWidgetActiveObserver {
    _90
    override fun onWidgetActive(widgetId: String) {
    _90
    createWidget(widgetId)
    _90
    }
    _90
    _90
    override fun onWidgetInActive(widgetId: String) {
    _90
    destroyWidget(widgetId)
    _90
    }
    _90
    }
    _90
    _90
    private fun createWidget(widgetId: String) {
    _90
    if (teachAidWidgets.contains(widgetId)) {
    _90
    AgoraLog?.w("$tag->'$widgetId' is already created, can not repeat create!")
    _90
    return
    _90
    }
    _90
    AgoraLog?.w("$tag->create teachAid that of '$widgetId'")
    _90
    // Here, the previously registered widgetConfigs are searched by widgetId
    _90
    val widgetConfig = eduContext?.widgetContext()?.getWidgetConfig(widgetId)
    _90
    widgetConfig?.let { config ->
    _90
    _90
    // Take widgetClass from widgetConfig and instantiate a Widget object instance through reflection
    _90
    val widget = eduContext?.widgetContext()?.create(config)
    _90
    widget?.let {
    _90
    AgoraLog?.w("$tag->successfully created '$widgetId'")
    _90
    when (widgetId) {
    _90
    CountDown.id -> {
    _90
    (it as? AgoraCountDownWidget)?.getWidgetMsgObserver()?.let { observer ->
    _90
    eduContext?.widgetContext()?.addWidgetMessageObserver(observer, widgetId)
    _90
    }
    _90
    }
    _90
    Selector.id -> {
    _90
    (it as? AgoraIClickerWidget)?.getWidgetMsgObserver()?.let { observer ->
    _90
    eduContext?.widgetContext()?.addWidgetMessageObserver(observer, widgetId)
    _90
    }
    _90
    }
    _90
    Polling.id -> {
    _90
    (it as? AgoraVoteWidget)?.getWidgetMsgObserver()?.let { observer ->
    _90
    eduContext?.widgetContext()?.addWidgetMessageObserver(observer, widgetId)
    _90
    }
    _90
    }
    _90
    }
    _90
    // record widget
    _90
    teachAidWidgets[widgetId] = widget
    _90
    // Create widgetContainer and bind to root Container
    _90
    val widgetContainer = managerWidgetsContainer(allWidgetsContainer = binding.root, widgetId = widgetId)
    _90
    AgoraLog?.i("$tag->successfully created '$widgetId' container")
    _90
    widgetContainer?.let { group ->
    _90
    AgoraLog?.w("$tag->initialize '$widgetId'")
    _90
    // Initialize the Widget
    _90
    widget.init(group)
    _90
    }
    _90
    }
    _90
    }
    _90
    }
    _90
    _90
    private fun destroyWidget(widgetId: String) {
    _90
    // remove from map
    _90
    val widget = teachAidWidgets.remove(widgetId)
    _90
    // remove UIDataProviderListener
    _90
    when (widgetId) {
    _90
    CountDown.id -> {
    _90
    (widget as? AgoraTeachAidCountDownWidget)?.getWidgetMsgObserver()?.let { observer ->
    _90
    eduContext?.widgetContext()?.removeWidgetMessageObserver(observer, widgetId)
    _90
    }
    _90
    }
    _90
    Selector.id -> {
    _90
    (widget as? AgoraTeachAidIClickerWidget)?.getWidgetMsgObserver()?.let { observer ->
    _90
    eduContext?.widgetContext()?.removeWidgetMessageObserver(observer, widgetId)
    _90
    }
    _90
    }
    _90
    Polling.id -> {
    _90
    (widget as? AgoraTeachAidVoteWidget)?.getWidgetMsgObserver()?.let { observer ->
    _90
    eduContext?.widgetContext()?.removeWidgetMessageObserver(observer, widgetId)
    _90
    }
    _90
    }
    _90
    AgoraCloudDisk.id -> {
    _90
    (widget as? AgoraTeachAidVoteWidget)?.getWidgetMsgObserver()?.let { observer ->
    _90
    eduContext?.widgetContext()?.removeWidgetMessageObserver(observer, widgetId)
    _90
    }
    _90
    }
    _90
    }
    _90
    widget?.let {
    _90
    handler.post { it.release() }
    _90
    it.container?.let { group ->
    _90
    managerWidgetsContainer(binding.root, widgetId, group)
    _90
    }
    _90
    }
    _90
    }

API Reference

AgoraBaseWidget class


_125
// Widget base class
_125
abstract class AgoraBaseWidget {
_125
// The parent layout of the current Widget
_125
var container: ViewGroup? = null
_125
_125
// Information about the current widget
_125
var widgetInfo: AgoraWidgetInfo? = null
_125
_125
// Initialize the current widget
_125
// @param container The parent layout of the current Widget
_125
open fun init(container: ViewGroup) {
_125
this.container = container
_125
}
_125
_125
// position and size ratio information of the current Widget to the remote
_125
// @param frame The position and size ratio information of the current Widget
_125
// @param contextCallback callback listener for synchronous operations
_125
protected fun updateSyncFrame(frame: AgoraWidgetFrame, contextCallback: EduContextCallback<Unit>? = null) {
_125
......
_125
}
_125
_125
// @param properties Map of custom room properties in the Widget to be updated.
_125
// Need to pass the full path: "a.b.c.d": value
_125
// @param cause custom update reason
_125
// @param contextCallback callback listener for update operations
_125
_125
protected fun updateRoomProperties(
_125
properties: MutableMap<String, Any>,
_125
cause: MutableMap<String, Any>,
_125
contextCallback: EduContextCallback<Unit>? = null
_125
) {
_125
......
_125
}
_125
_125
// @param keys The set of key values ​​for custom user properties in the widget that needs to be deleted.
_125
// Need to pass the full path: a.b.c.d
_125
// @param cause custom delete reason
_125
// @param contextCallback callback listener for delete operation
_125
protected fun deleteRoomProperties(
_125
keys: MutableList<String>,
_125
cause: MutableMap<String, Any>,
_125
contextCallback: EduContextCallback<Unit>? = null
_125
) {
_125
......
_125
}
_125
_125
// @param properties Map of custom user properties within the Widget to be updated. Need to pass the full path: "a.b.c.d": value
_125
// @param cause custom update reason
_125
// @param contextCallback callback listener for update operations
_125
protected fun updateUserProperties(
_125
properties: MutableMap<String, Any>,
_125
cause: MutableMap<String, Any>,
_125
contextCallback: EduContextCallback<Unit>? = null
_125
) {
_125
......
_125
}
_125
_125
// @param keys The set of key values ​​for custom user properties in the widget that needs to be deleted. Need to pass the full path: a.b.c.d
_125
// @param cause custom delete reason
_125
// @param contextCallback callback listener for delete operation
_125
protected fun deleteUserProperties(
_125
keys: MutableList<String>,
_125
cause: MutableMap<String, Any>,
_125
contextCallback: EduContextCallback<Unit>? = null
_125
) {
_125
......
_125
}
_125
_125
// Send messages from inside the Widget to the outside
_125
protected fun sendMessage(message: String) {
_125
......
_125
}
_125
_125
// Widget's position and size changed
_125
// @params frame Widget position and size scale information
_125
open fun onSyncFrameUpdated(frame: AgoraWidgetFrame) {
_125
}
_125
_125
// Receive messages from outside the widget. The message comes from sendMessageToWidget in widgetContext
_125
open fun onMessageReceived(message: String) {
_125
}
_125
_125
// Callback for the update of local user information
_125
open fun onLocalUserInfoUpdated(info: AgoraWidgetUserInfo) {
_125
widgetInfo?.let {
_125
......
_125
}
_125
}
_125
_125
// Callback when room information is updated
_125
open fun onRoomInfoUpdated(info: AgoraWidgetRoomInfo) {
_125
widgetInfo?.let {
_125
......
_125
}
_125
}
_125
_125
// Callback for when the custom room property in the widget is updated
_125
open fun onWidgetRoomPropertiesUpdated(properties: MutableMap<String, Any>, cause: MutableMap<String, Any>?, keys: MutableList<String>) {
_125
......
_125
}
_125
_125
// Callback when custom room property in Widget is deleted
_125
open fun onWidgetRoomPropertiesDeleted(properties: MutableMap<String, Any>, cause: MutableMap<String, Any>?, keys: MutableList<String>) {
_125
......
_125
}
_125
_125
// Callback for when custom local user properties in Widget are updated
_125
open fun onWidgetUserPropertiesUpdated(properties: MutableMap<String, Any>,
_125
cause: MutableMap<String, Any>?,
_125
keys: MutableList<String>) {
_125
......
_125
}
_125
_125
// The callback for the deletion of the custom local user attribute in the Widget
_125
open fun onWidgetUserPropertiesDeleted(properties: MutableMap<String, Any>,
_125
cause: MutableMap<String, Any>?,
_125
keys: MutableList<String>) {
_125
......
_125
}
_125
_125
// Release resources
_125
open fun release() {
_125
......
_125
}
_125
}

AgoraWidgetContext interface


_62
// WidgetContext capability interface
_62
interface AgoraWidgetContext {
_62
// Create a Widget object instance
_62
// @param config The configuration information of this Widget object
_62
// @return Widget instance, if it is empty, it means the creation failed
_62
fun create(config: AgoraWidgetConfig): AgoraBaseWidget?
_62
_62
// Get the current position and size ratio information of the Widget
_62
// @param widgetId Widget's unique identifier
_62
// @return widget's current position and scale information. The benchmark of the ratio needs to be unified by the developers themselves.
_62
fun getWidgetSyncFrame(widgetId: String): AgoraWidgetFrame?
_62
_62
// Get the configuration information of all registered widgets
_62
fun getWidgetConfigs(): MutableList<AgoraWidgetConfig>
_62
_62
// Get the configuration information of a registered widget
_62
// @param widgetId Widget's unique identifier
_62
fun getWidgetConfig(widgetId: String): AgoraWidgetConfig?
_62
_62
// Activate a widget.
_62
// After the operation is successful, you will receive a callback from AgoraWidgetActiveObserver.onWidgetActive
_62
// @param widgetId Widget's unique identifier
_62
// @param ownerUserUuid The userUuid of the user who owns the currently activated Widget
_62
// @param roomProperties initialized room properties
_62
// @param callback The callback listener for the activation operation
_62
fun setWidgetActive(widgetId: String, ownerUserUuid: String? = null,
_62
roomProperties: Map<String, Any>? = null,
_62
callback: EduContextCallback<Unit>? = null)
_62
_62
// Log out the specified Widget
_62
// @param widgetId Widget's unique identifier
_62
// @param isRemove Whether to completely delete all the information of this Widget in the current classroom:
_62
// - true: delete completely. All information under roomProperties.widgets.'widgetId' and userProperties.widgets.'widgetId' will be deleted
_62
// - false: Only set roomProperties.widgets.'widgetId'.state to '0', that is, set the current Widget to inactive state.
_62
// No matter what value is passed, you will receive the AgoraWidgetActiveObserver.onWidgetInActive callback.
_62
// @param callback callback listener for logout operation
_62
fun setWidgetInActive(widgetId: String, isRemove: Boolean = false, callback: EduContextCallback<Unit>? = null)
_62
_62
_62
// Get the activation state of a widget
_62
// @param widgetId Widget's unique identifier
_62
fun getWidgetActive(widgetId: String): Boolean
_62
_62
// Get the activation status of all registered widgets
_62
// @param widgetId Widget's unique identifier
_62
fun getAllWidgetActive(): Map<String, Boolean>
_62
_62
// Add an AgoraWidgetActiveObserver listener to monitor whether the Widget is active
_62
fun addWidgetActiveObserver(observer: AgoraWidgetActiveObserver, widgetId: String)
_62
_62
// Remove an AgoraWidgetActiveObserver listener
_62
fun removeWidgetActiveObserver(observer: AgoraWidgetActiveObserver, widgetId: String)
_62
_62
// Add an AgoraWidgetMessageObserver listener to listen for all messages sent by this Widget
_62
fun addWidgetMessageObserver(observer: AgoraWidgetMessageObserver, widgetId: String)
_62
_62
// Remove an AgoraWidgetMessageObserver listener
_62
fun removeWidgetMessageObserver(observer: AgoraWidgetMessageObserver, widgetId: String)
_62
_62
// Send a message to a widget
_62
fun sendMessageToWidget(msg: String, widgetId: String)
_62
}

Type definition


_49
// Widget configuration class
_49
data class AgoraWidgetConfig(
_49
// The custom Widget class corresponding to this WidgetId
_49
var widgetClass: Class<out AgoraBaseWidget>,
_49
// Unique identifier for the current Widget
_49
var widgetId: String,
_49
// The icon in the unselected (default or inactive) state
_49
val image: Int? = null,
_49
// The icon in the selected or activated state
_49
val selectedImage: Int? = null,
_49
// Custom data, which is passed to AgoraWidgetInfo.extraInfo after Widget is instantiated
_49
var extraInfo: Any? = null
_49
)
_49
_49
// Widget information class
_49
data class AgoraWidgetInfo(
_49
var roomInfo: AgoraWidgetRoomInfo,
_49
var localUserInfo: AgoraWidgetUserInfo,
_49
// Unique identifier for the current Widget
_49
val widgetId: String,
_49
// Custom data passed in externally
_49
val extraInfo: Any?,
_49
// custom room properties in the current widget
_49
var roomProperties: MutableMap<String, Any>?,
_49
// Custom local user properties in the current Widget
_49
var localUserProperties: MutableMap<String, Any>?
_49
)
_49
_49
// Widget's position and size ratio information class
_49
data class AgoraWidgetFrame(
_49
val x: Float? = null,
_49
val y: Float? = null,
_49
val width: Float? = null,
_49
val height: Float? = null
_49
)
_49
_49
// User information class in Widget
_49
data class AgoraWidgetUserInfo(
_49
val userUuid: String,
_49
val userName: String,
_49
val userRole: Int
_49
)
_49
_49
// Room information class in Widget
_49
data class AgoraWidgetRoomInfo(
_49
val roomUuid: String,
_49
val roomName: String,
_49
val roomType: Int
_49
)