SuperAPI AI Directory

kuikly-expand-api

Coding & development

View repository

Updated 10-15 23:43

About this skill

Kuikly Custom Module Development

Contents

  • Core Guidelines
  • Workflow: Creating Custom Module (Kuikly Side)
  • Workflow: Implementing Native Side Module

Core Guidelines

  • Module is the unified interface for platform APIs: Kuikly itself does not have platform-related capabilities; through the Module mechanism, Native APIs are exposed for the Kuikly side to call.
  • A custom Module requires implementation on both sides: The Kuikly side defines the API interface, and the Native side (Android/iOS/HarmonyOS/H5/Mini Program) implements the specific logic, associated through moduleName association.
  • Communication methods are divided into synchronous and asynchronous: Synchronous calls (syncToNativeMethod) execute on the Kuikly thread, asynchronous calls (asyncToNativeMethod) execute on the main thread. Synchronous calls should avoid time-consuming operations.
  • The Module name must be consistent across all platforms: On the Kuikly side, moduleName() the return value must exactly match the name registered on the Native side.

Workflow: Creating Module (Kuikly Side)

Use this workflow to create a custom Module on the Kuikly (Kotlin) side.

Steps:

  1. Create a new Module class that inherits Module, and implement the moduleName() method.
  2. Write business API methods, and communicate with Native through toNative / syncToNativeMethod / asyncToNativeMethod .
  3. Register the Module in the Pager's createExternalModules() .
  4. Obtain and use it in business code through acquireModule .

Step 1: Define the Module class

import com.tencent.kuikly.core.module.Module
import com.tencent.kuikly.core.module.CallbackFn
import com.tencent.kuikly.core.nvi.serialization.json.JSONObject

class MyLogModule : Module() {

    override fun moduleName(): String = "KRMyLogModule"

    companion object {
        const val MODULE_NAME = "KRMyLogModule"
    }
}

Step 2: Write API methods

Module provides the following communication methods:

Method Call Type Native Execution Thread Parameter Type Callback Type
syncToNativeMethod(methodName, JSONObject?, CallbackFn?) Synchronous Kuikly thread JSONObject (serialized to JSON string) CallbackFn(JSONObject?)
syncToNativeMethod(methodName, Array<Any>, AnyCallbackFn?) Synchronous Kuikly thread Primitive type array (String/Int/Float/ByteArray) AnyCallbackFn(Any?)
asyncToNativeMethod(methodName, JSONObject?, CallbackFn?) Asynchronous Main thread JSONObject (serialized to JSON string) CallbackFn(JSONObject?)
asyncToNativeMethod(methodName, Array<Any>, AnyCallbackFn?) Asynchronous Main thread Primitive type array (String/Int/Float/ByteArray) AnyCallbackFn(Any?)
toNative(keepCallbackAlive, methodName, param, callback, syncCall) Generic low-level method Depends on syncCall Any? CallbackFn(JSONObject?)

Asynchronous call (no return value):

fun log(content: String) {
    asyncToNativeMethod(
        "log",
        JSONObject().apply { put("content", content) },
        null
    )
}

Asynchronous call (with callback):

fun logWithCallback(content: String, callbackFn: CallbackFn) {
    asyncToNativeMethod(
        "logWithCallback",
        JSONObject().apply { put("content", content) },
        callbackFn
    )
}

Synchronous call (with return value):

fun syncLog(content: String): String {
    return syncToNativeMethod(
        "syncLog",
        JSONObject().apply { put("content", content) },
        null
    )
}

Binary data transfer (using the Array parameter):

fun uploadData(bytes: ByteArray): Any? {
    return syncToNativeMethod(
        "uploadData",
        arrayOf<Any>(bytes),
        null
    )
}

Resident callback (keepCallbackAlive = true):

fun registerListener(callbackFn: CallbackFn): CallbackRef {
    val result = toNative(
        true,           // keepCallbackAlive: callback 不会被自动销毁
        "registerListener",
        null,
        callbackFn,
        false
    )
    return result.callbackRef!!
}

// 手动移除回调
fun unregisterListener(callbackRef: CallbackRef) {
    removeCallback(callbackRef)
}

Step 3: Register the Module

A custom Module needs to be registered in the Pager or ComposeContainer's createExternalModules() , and the registration method is the same for both DSLs.

Kuikly DSL registration:

import com.tencent.kuikly.core.annotations.Page
import com.tencent.kuikly.core.module.Module

@Page("MyPage")
internal class MyPage : Pager() {

    override fun createExternalModules(): Map<String, Module>? {
        return mapOf(
            MyLogModule.MODULE_NAME to MyLogModule()
        )
    }

    // 如果有基类 BasePager 已注册了其他 Module,需要合并:
    // override fun createExternalModules(): Map<String, Module>? {
    //     val modules = (super.createExternalModules() as? HashMap) ?: hashMapOf()
    //     modules[MyLogModule.MODULE_NAME] = MyLogModule()
    //     return modules
    // }

    override fun body(): ViewBuilder { ... }
}

Compose DSL registration:

import com.tencent.kuikly.compose.ComposeContainer
import com.tencent.kuikly.compose.setContent
import com.tencent.kuikly.core.annotations.Page
import com.tencent.kuikly.core.module.Module

@Page("MyComposePage")
class MyComposePage : ComposeContainer() {

    // 注册方式与 Kuikly DSL 完全一致
    override fun createExternalModules(): Map<String, Module>? {
        return mapOf(
            MyLogModule.MODULE_NAME to MyLogModule()
        )
    }

    override fun willInit() {
        super.willInit()
        setContent {
            MyScreen()
        }
    }
}

Step 4: Use the Module

Usage in Kuikly DSL:

override fun created() {
    super.created()

    val myLogModule = acquireModule<MyLogModule>(MyLogModule.MODULE_NAME)

    // 异步调用
    myLogModule.log("test log")

    // 异步调用带回调
    myLogModule.logWithCallback("log with callback") { data ->
        val result = data  // Native 侧返回的 JSONObject
    }

    // 同步调用
    val result = myLogModule.syncLog("sync log")
}

Usage in Compose DSL:

import androidx.compose.runtime.Composable
import com.tencent.kuikly.compose.ui.platform.LocalActivity
import com.tencent.kuikly.core.pager.Pager

@Composable
fun MyScreen() {
    val pager = LocalActivity.current.getPager() as Pager
    val myLogModule = pager.acquireModule<MyLogModule>(MyLogModule.MODULE_NAME)

    // 异步调用
    myLogModule.log("test log")

    // 异步调用带回调
    myLogModule.logWithCallback("log with callback") { data ->
        val result = data  // Native 侧返回的 JSONObject
    }

    // 同步调用
    val result = myLogModule.syncLog("sync log")
}

Workflow: Implementing Native Side Module

Use this workflow to implement the Native side of a custom Module.

Prerequisites: You only need to implement the Native-side Module on platforms that the business needs to support; there is no need to implement all platforms.

Steps:

  1. Determine the target platforms that need to be supported (Android/iOS/HarmonyOS/H5/Mini Program).
  2. Create the corresponding Module class in the host project of the target platform.
  3. Implement the call method, and dispatch processing according to method .
  4. Register the Module with the Kuikly framework.

💡 HarmonyOS platform note: HarmonyOS provides two implementation approaches, ArkTS and C; in general, you only need to choose the ArkTS implementation.

For platform-specific implementations, see MODULE_IMPLEMENT.md

Quick reference: Module base classes and registration methods for each platform

Platform Base class Registration method Notes
Android KuiklyRenderBaseModule() registerExternalModule in moduleExport(name) { Module() } Override call(method, params: Any?, callback) or call(method, params: String?, callback)
iOS KRBaseModule The class name must match the moduleName on the Kuikly side (dynamically created at runtime) The method name must match the methodName on the Kuikly side, and the parameter is fixed as NSDictionary, Swift requires the @objc annotation
HarmonyOS (ArkTS) KuiklyRenderBaseModule getCustomRenderModuleCreatorRegisterMap register in Must implement syncMode() to specify synchronous/asynchronous mode
HarmonyOS (C) KRRenderModuleRegisterV2 InitKuikly register in The return value KRAnyData is released by the framework
H5 KuiklyRenderBaseModule() registerExternalModule in moduleExport(name) { Module() } Same as Android
Mini Program KuiklyRenderBaseModule() registerExternalModule in moduleExport(name) { Module() } Can access the NativeApi.plat object via wx object

Data types supported on the Native side

Platform Supported data types
Android String Int Long Float Double Boolean ByteArray Map List JSONObject
iOS NSString NSNumber BOOL NSData NSDictionary NSArray
HarmonyOS String Int Long Float Double Bool ByteArray Array Map/Record
H5/Mini Program String Int Long Float Double Boolean Array Map List JSONObject JSONArray

Data serialization rules

Category Serialization method Involved types
Basic types Direct pass-through String Int Float Double Boolean NSNumber
Binary data Direct pass-through ByteArray NSData ArrayBuffer
JSON data JSON string JSONObject JSONArray
Collection types JSON string Map/Record List NSDictionary NSArray Array
Special rules Direct pass-through When Array contains ByteArray/NSData when

⚠️ syncToNativeMethod and asyncToNativeMethod When passing a JSONObject parameter, it will be serialized into a JSON string, and ByteArray binary data is not supported. To transmit binary, use the Array<Any> parameter overload method.


Common pitfalls and correct practices

❌ Incorrect practice ✅ Correct practice
The Module name on the Kuikly side is inconsistent with the Native side Ensure that the moduleName() return value exactly matches the Native registration name
Get the Module before created Get Module before Get the Module at or after created() (Kuikly DSL); get it after willInit in setContent (Compose DSL)
Performing time-consuming operations in synchronous calls Synchronous calls execute on the Kuikly thread; avoid time-consuming operations blocking rendering
Forgetting to register the custom Module in createExternalModules Register custom Module in The custom Module must be registered in the Pager's createExternalModules() register in
Using a JSONObject parameter to transmit binary data Use the Array<Any> of the syncToNativeMethod/asyncToNativeMethod parameter to transmit ByteArray
iOS-side Module class name does not match Kuikly side iOS dynamically creates Module by class name, and the class name must match moduleName() be consistent
Implemented in Swift on iOS side but missing @objc annotation A Module implemented in Swift requires @objc or @objcMembers modifier
Resident callback forgotten to be manually removed keepCallbackAlive=true The callback of needs to be called before the page is destroyed removeCallback
Call directly in Compose DSL acquireModule In @Composable in the function, obtain via LocalActivity.current.getPager().acquireModule get
Forgot to import in Compose DSL LocalActivity requires import com.tencent.kuikly.compose.ui.platform.LocalActivity