You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose服务端驱动UI:交互组件渲染与值回传及示例JSON

在Jetpack Compose中实现服务端驱动UI的交互组件与数据回传

核心思路

服务端驱动UI(SDUI)的交互组件实现核心在于:

  • 服务端通过JSON定义组件的类型、属性、交互规则
  • 客户端解析JSON后渲染对应Compose组件,统一维护本地输入状态
  • 提交时收集所有交互组件的状态,打包回传给服务端

示例SDUI交互组件JSON

{
  "screen": {
    "title": "用户信息表单",
    "components": [
      {
        "type": "TEXT_INPUT",
        "id": "username",
        "label": "用户名",
        "hint": "请输入用户名",
        "inputType": "TEXT"
      },
      {
        "type": "DROP_DOWN",
        "id": "user_role",
        "label": "用户角色",
        "options": [
          {"value": "admin", "displayText": "管理员"},
          {"value": "editor", "displayText": "编辑"},
          {"value": "viewer", "displayText": "查看者"}
        ],
        "defaultValue": "viewer"
      },
      {
        "type": "BUTTON",
        "id": "submit_btn",
        "text": "提交",
        "action": {
          "type": "SUBMIT_FORM",
          "endpoint": "/api/submit-user-info"
        }
      }
    ]
  }
}

Compose端实现步骤

1. 定义数据模型

将JSON结构映射为Kotlin数据类,用密封类区分组件类型:

// 顶层屏幕模型
data class SduiScreen(
    val title: String,
    val components: List<SduiComponent>
)

// 组件密封基类
sealed class SduiComponent(val id: String) {
    data class TextInput(
        val id: String,
        val label: String,
        val hint: String,
        val inputType: String
    ) : SduiComponent(id)

    data class DropDown(
        val id: String,
        val label: String,
        val options: List<DropDownOption>,
        val defaultValue: String
    ) : SduiComponent(id)

    data class Button(
        val id: String,
        val text: String,
        val action: ButtonAction
    ) : SduiComponent(id)
}

data class DropDownOption(val value: String, val displayText: String)
data class ButtonAction(val type: String, val endpoint: String)

2. 统一管理交互状态

使用mutableStateMapOf维护所有组件的输入值,适配动态新增组件的场景:

// 在Compose屏幕中初始化状态
val inputStates = remember { mutableStateMapOf<String, String>() }

3. 解析并渲染交互组件

根据组件类型渲染对应Compose组件,绑定状态并处理交互:

@Composable
fun SduiScreenRenderer(screen: SduiScreen, onSubmit: (Map<String, String>, String) -> Unit) {
    val inputStates = remember { mutableStateMapOf<String, String>() }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        Text(text = screen.title, style = MaterialTheme.typography.headlineSmall)

        screen.components.forEach { component ->
            when (component) {
                is SduiComponent.TextInput -> {
                    // 初始化默认空值
                    LaunchedEffect(Unit) {
                        if (!inputStates.containsKey(component.id)) {
                            inputStates[component.id] = ""
                        }
                    }

                    OutlinedTextField(
                        value = inputStates[component.id] ?: "",
                        onValueChange = { inputStates[component.id] = it },
                        label = { Text(component.label) },
                        placeholder = { Text(component.hint) },
                        modifier = Modifier.fillMaxWidth()
                    )
                }
                is SduiComponent.DropDown -> {
                    // 初始化默认选中值
                    LaunchedEffect(Unit) {
                        if (!inputStates.containsKey(component.id)) {
                            inputStates[component.id] = component.defaultValue
                        }
                    }

                    var expanded by remember { mutableStateOf(false) }
                    val selectedOption = component.options.find { it.value == inputStates[component.id] }

                    ExposedDropdownMenuBox(
                        expanded = expanded,
                        onExpandedChange = { expanded = !expanded }
                    ) {
                        OutlinedTextField(
                            readOnly = true,
                            value = selectedOption?.displayText ?: "",
                            onValueChange = {},
                            label = { Text(component.label) },
                            trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
                            modifier = Modifier
                                .fillMaxWidth()
                                .menuAnchor()
                        )
                        ExposedDropdownMenu(
                            expanded = expanded,
                            onDismissRequest = { expanded = false }
                        ) {
                            component.options.forEach { option ->
                                DropdownMenuItem(
                                    text = { Text(option.displayText) },
                                    onClick = {
                                        inputStates[component.id] = option.value
                                        expanded = false
                                    }
                                )
                            }
                        }
                    }
                }
                is SduiComponent.Button -> {
                    Button(
                        onClick = {
                            if (component.action.type == "SUBMIT_FORM") {
                                onSubmit(inputStates.toMap(), component.action.endpoint)
                            }
                        },
                        modifier = Modifier.align(Alignment.CenterHorizontally)
                    ) {
                        Text(component.text)
                    }
                }
            }
        }
    }
}

4. 提交数据到服务端

实现异步提交逻辑,将收集到的状态发送到指定服务端接口:

// 示例提交函数,实际可替换为Retrofit/OkHttp实现
fun submitFormData(formData: Map<String, String>, endpoint: String) {
    // 构建请求体并发送POST请求
    println("提交数据到${endpoint}: ${formData}")
    // 实际代码示例(Retrofit):
    // apiService.submitForm(endpoint, formData).enqueue(object : Callback<ResponseBody> { ... })
}

5. 整合使用

在主Compose函数中加载并渲染SDUI屏幕:

@Composable
fun MainScreen() {
    // 模拟从服务端获取并解析JSON(实际用Gson/Moshi解析字符串)
    val screen = remember {
        SduiScreen(
            title = "用户信息表单",
            components = listOf(
                SduiComponent.TextInput(
                    id = "username",
                    label = "用户名",
                    hint = "请输入用户名",
                    inputType = "TEXT"
                ),
                SduiComponent.DropDown(
                    id = "user_role",
                    label = "用户角色",
                    options = listOf(
                        DropDownOption("admin", "管理员"),
                        DropDownOption("editor", "编辑"),
                        DropDownOption("viewer", "查看者")
                    ),
                    defaultValue = "viewer"
                ),
                SduiComponent.Button(
                    id = "submit_btn",
                    text = "提交",
                    action = ButtonAction("SUBMIT_FORM", "/api/submit-user-info")
                )
            )
        )
    }

    SduiScreenRenderer(screen) { formData, endpoint ->
        submitFormData(formData, endpoint)
    }
}

关键注意点

  • 状态扩展性:使用StateMap而非单个状态变量,避免新增组件时修改状态逻辑
  • 输入验证:可在JSON中添加validation字段(如必填、格式规则),客户端解析后实现校验
  • UI兼容性:根据JSON中的inputType适配不同的键盘类型(如数字、邮箱)
  • 错误处理:提交时需处理网络异常、服务端返回的错误信息

内容的提问来源于stack exchange,提问作者Swati

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 01:08:26