Core Products
Add-Ons
- Extensions Marketplace
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.
-
Define the class
_29class FCRCloudDiskWidget : AgoraBaseWidget() {_29override val tag = "AgoraEduNetworkDiskWidget"_29_29private var cloudDiskContent: AgoraEduCloudDiskWidgetContent? = null_29_29// Initialize Widget_29// Add your custom UI layout to the container here_29override fun init(container: ViewGroup) {_29super.init(container)_29container.post {_29widgetInfo?.let {_29// Instantiate content and add CloudDisk view to container_29cloudDiskContent = AgoraEduCloudDiskWidgetContent(container, it)_29}_29}_29}_29_29// Release used resources_29override fun release() {_29cloudDiskContent?.dispose()_29super.release()_29}_29_29// The content class is only for encapsulation of the code._29// Functional details are placed in the content._29private inner class AgoraEduCloudDiskWidgetContent(val container: ViewGroup, val widgetInfo: AgoraWidgetInfo) {_29......_29}_29}The complete code can be viewed in the CloudClass-Android repository.
-
Configure the plugin in to register it with the Agora Classroom SDK
_21private fun configPublicCourseware(launchConfig: AgoraEduLaunchConfig) {_21val courseware0 = CoursewareUtil.transfer(DefaultPublicCoursewareJson.data0)_21val courseware1 = CoursewareUtil.transfer(DefaultPublicCoursewareJson.data1)_21val publicCoursewares = ArrayList<AgoraEduCourseware>(2)_21publicCoursewares.add(courseware0)_21publicCoursewares.add(courseware1)_21_21// custom data for map structure_21val cloudDiskExtra = mutableMapOf<String, Any>()_21cloudDiskExtra[publicResourceKey] = publicCoursewares_21cloudDiskExtra[configKey] = Pair(launchConfig.appId, launchConfig.userUuid)_21val widgetConfigs = mutableListOf<AgoraWidgetConfig>()_21widgetConfigs.add(_21// Instantiate widgetConfig and add it to widgetConfig collection_21AgoraWidgetConfig(widgetClass = FCRCloudDiskWidget::class.java, widgetId = AgoraWidgetDefaultId.AgoraCloudDisk.id,_21extraInfo = 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._21launchConfig.widgetConfigs = widgetConfigs_21} -
Instantiate and initialize the widget
_90// Add an AgoraWidgetActiveObserver through widgetContext.addWidgetActiveObserver_90// to monitor the activation and deregistration of the Widget_90private val widgetActiveObserver = object : AgoraWidgetActiveObserver {_90override fun onWidgetActive(widgetId: String) {_90createWidget(widgetId)_90}_90_90override fun onWidgetInActive(widgetId: String) {_90destroyWidget(widgetId)_90}_90}_90_90private fun createWidget(widgetId: String) {_90if (teachAidWidgets.contains(widgetId)) {_90AgoraLog?.w("$tag->'$widgetId' is already created, can not repeat create!")_90return_90}_90AgoraLog?.w("$tag->create teachAid that of '$widgetId'")_90// Here, the previously registered widgetConfigs are searched by widgetId_90val widgetConfig = eduContext?.widgetContext()?.getWidgetConfig(widgetId)_90widgetConfig?.let { config ->_90_90// Take widgetClass from widgetConfig and instantiate a Widget object instance through reflection_90val widget = eduContext?.widgetContext()?.create(config)_90widget?.let {_90AgoraLog?.w("$tag->successfully created '$widgetId'")_90when (widgetId) {_90CountDown.id -> {_90(it as? AgoraCountDownWidget)?.getWidgetMsgObserver()?.let { observer ->_90eduContext?.widgetContext()?.addWidgetMessageObserver(observer, widgetId)_90}_90}_90Selector.id -> {_90(it as? AgoraIClickerWidget)?.getWidgetMsgObserver()?.let { observer ->_90eduContext?.widgetContext()?.addWidgetMessageObserver(observer, widgetId)_90}_90}_90Polling.id -> {_90(it as? AgoraVoteWidget)?.getWidgetMsgObserver()?.let { observer ->_90eduContext?.widgetContext()?.addWidgetMessageObserver(observer, widgetId)_90}_90}_90}_90// record widget_90teachAidWidgets[widgetId] = widget_90// Create widgetContainer and bind to root Container_90val widgetContainer = managerWidgetsContainer(allWidgetsContainer = binding.root, widgetId = widgetId)_90AgoraLog?.i("$tag->successfully created '$widgetId' container")_90widgetContainer?.let { group ->_90AgoraLog?.w("$tag->initialize '$widgetId'")_90// Initialize the Widget_90widget.init(group)_90}_90}_90}_90}_90_90private fun destroyWidget(widgetId: String) {_90// remove from map_90val widget = teachAidWidgets.remove(widgetId)_90// remove UIDataProviderListener_90when (widgetId) {_90CountDown.id -> {_90(widget as? AgoraTeachAidCountDownWidget)?.getWidgetMsgObserver()?.let { observer ->_90eduContext?.widgetContext()?.removeWidgetMessageObserver(observer, widgetId)_90}_90}_90Selector.id -> {_90(widget as? AgoraTeachAidIClickerWidget)?.getWidgetMsgObserver()?.let { observer ->_90eduContext?.widgetContext()?.removeWidgetMessageObserver(observer, widgetId)_90}_90}_90Polling.id -> {_90(widget as? AgoraTeachAidVoteWidget)?.getWidgetMsgObserver()?.let { observer ->_90eduContext?.widgetContext()?.removeWidgetMessageObserver(observer, widgetId)_90}_90}_90AgoraCloudDisk.id -> {_90(widget as? AgoraTeachAidVoteWidget)?.getWidgetMsgObserver()?.let { observer ->_90eduContext?.widgetContext()?.removeWidgetMessageObserver(observer, widgetId)_90}_90}_90}_90widget?.let {_90handler.post { it.release() }_90it.container?.let { group ->_90managerWidgetsContainer(binding.root, widgetId, group)_90}_90}_90}
API Reference
AgoraBaseWidget
class
_125// Widget base class_125abstract 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_62interface 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_49data 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_49data 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_49data 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_49data class AgoraWidgetUserInfo(_49 val userUuid: String,_49 val userName: String,_49 val userRole: Int_49)_49_49// Room information class in Widget_49data class AgoraWidgetRoomInfo(_49 val roomUuid: String,_49 val roomName: String,_49 val roomType: Int_49)