ติดตั้งใช้งานคอมโพเนนต์ A2UI ที่กำหนดเอง

ในสถาปัตยกรรม A2UI ทุกแพลตฟอร์มจะขับเคลื่อนโดยแคตตาล็อกคอมโพเนนต์ แคตตาล็อกจะประกาศคอมโพเนนต์ สคีมาพร็อพเพอร์ตี้ และความสามารถที่พร้อมใช้งานสำหรับ AI Agent แทนที่จะให้ AI Agent สร้างองค์ประกอบ UI ของตัวเองหรือสร้างโค้ดที่กำหนดเอง จากนั้น Agent จะใช้คอมโพเนนต์เหล่านี้เพื่อ สร้างอินเทอร์เฟซผู้ใช้

เมื่อสร้างแคตตาล็อกที่กำหนดเองสำหรับระบบการออกแบบของแอป คุณจะ ใช้คอมโพเนนต์ที่แมปคำจำกัดความของแคตตาล็อกเหล่านั้นกับองค์ประกอบ UI ของ Jetpack Compose ที่เฉพาะเจาะจง คอมโพเนนต์ A2UI แต่ละรายการ (A2uiComponent) จะกำหนดสัญญาของสคีมาพร็อพเพอร์ตี้ ประเมินความพร้อมเมื่อข้อมูลแบบไดนามิกมาถึง ผูกพร็อพเพอร์ตี้แบบรีแอกทีฟจากโมเดลข้อมูล ปล่อย UI ของ Compose และส่งการดำเนินการโต้ตอบของผู้ใช้กลับไปยังเอเจนต์

ตัวแสดงผล UI ของ Compose (androidx.a2ui.compose:compose-ui) มีอินเทอร์เฟซและขอบเขตตัวรับที่จำเป็นต่อการใช้คอมโพเนนต์ที่กำหนดเอง ซึ่งเป็นไปตามระบบการออกแบบของแอป

ประกาศพร็อพเพอร์ตี้ของคอมโพเนนต์ที่พิมพ์แบบคงที่

ก่อนที่จะแสดงผล ให้ประกาศพร็อพเพอร์ตี้ที่คอมโพเนนต์คาดหวังจากเอเจนต์ เลเยอร์รันไทม์มี A2uiProperty API ที่พิมพ์แบบคงที่ซึ่งใช้ ทั้งในการสร้างสคีมา JSON และการแยกค่าที่รันไทม์

// Define static properties, dynamic bindings, and component references
val textProp = A2uiProperty.dynamicString("text", required = true)
val variantProp = A2uiProperty.stringEnum("variant", enumValues = listOf("body", "title"))
val childProp = A2uiProperty.componentId("child", required = true)
val actionProp = A2uiProperty.action("action", required = true)

ใช้ส่วนติดต่อ A2uiComponent

ใช้A2uiComponentอินเทอร์เฟซเพื่อกำหนดสคีมาของคอมโพเนนต์และแมปพร็อพเพอร์ตี้ที่ได้รับจาก Agent ไปยัง Compose UI ดังนี้

object CustomTextComponent : A2uiComponent {
    private val textProp = A2uiProperty.dynamicString("text", required = true)
    private val variantProp = A2uiProperty.stringEnum(
        "variant",
        enumValues = listOf("body", "title"),
    )

    override val name = "Text"
    override val description = "Displays dynamic text."
    override val properties = listOf(textProp, variantProp)

    @Composable
    override fun A2uiComponentScope.isReady(properties: A2uiComponentProperties): Boolean {
        // The component does not become ready until dynamic text data arrives
        return properties.bind(textProp) != null
    }

    @Composable
    override fun A2uiComponentScope.Content(
        properties: A2uiComponentProperties,
        modifier: Modifier,
    ) {
        // Reactively resolve dynamic data binding and subscribe to updates
        val text = properties.bind(textProp) ?: ""

        // Read the static configuration property
        val variant = properties[variantProp] ?: "body"
        val textStyle = if (variant == "title") {
            MaterialTheme.typography.titleLarge
        } else {
            MaterialTheme.typography.bodyLarge
        }

        Text(
            text = text,
            style = textStyle,
            modifier = modifier,
        )
    }
}

แก้ไขการเชื่อมโยงโมเดลข้อมูลแบบปกติและแบบ 2 ทาง

การใช้งานคอมโพเนนต์ใช้ A2uiComponentScope เพื่อแก้ปัญหาพร็อพเพอร์ตี้ที่เชื่อมโยงแบบไดนามิก สำหรับพร็อพเพอร์ตี้แบบไดนามิกปกติ bind จะแสดงค่าปัจจุบันและ สมัครรับข้อมูลการอัปเดตโมเดลข้อมูลโดยอัตโนมัติ

สำหรับคอมโพเนนต์อินพุตแบบอินเทอร์แอกทีฟ bindUpdater จะแสดงผล Lambda ของโปรแกรมอัปเดตที่เสถียร หากตัวแทนระบุสตริงตามตัวอักษรแทนเส้นทางข้อมูลที่เขียนได้ Lambda ของโปรแกรมอัปเดตจะnull ซึ่งเป็นการส่งสัญญาณว่าฟิลด์เป็นแบบอ่านอย่างเดียว

val labelProp = A2uiProperty.dynamicString("label", required = true)
val valueProp = A2uiProperty.dynamicBoolean("value")

@Composable
fun A2uiComponentScope.CustomCheckbox(properties: A2uiComponentProperties) {
    // Read a dynamic property from the data model subscribing to updates
    val label = properties.bind(labelProp) ?: ""

    // Bind a property value and its updater to handle two-way data binding
    val checked = properties.bind(valueProp) ?: false
    val onCheckedChange = properties.bindUpdater(valueProp)

    Row(verticalAlignment = Alignment.CenterVertically) {
        Checkbox(
            checked = checked,
            onCheckedChange = onCheckedChange,
            enabled = (onCheckedChange != null), // Read-only if no writable path was bound
        )
        Text(text = label)
    }
}

ส่งการดำเนินการของผู้ใช้ไปยัง Agent

คอมโพเนนต์แบบอินเทอร์แอกทีฟใช้ A2uiComponentScope.dispatchAction เพื่อส่งเหตุการณ์ของผู้ใช้กลับไปยัง Agent

object CustomButtonComponent : A2uiComponent {
    private val childProp = A2uiProperty.componentId("child", required = true)
    private val actionProp = A2uiProperty.action("action", required = true)

    override val name = "Button"
    override val description = "A clickable button."
    override val properties = listOf(childProp, actionProp)

    @Composable
    override fun A2uiComponentScope.Content(
        properties: A2uiComponentProperties,
        modifier: Modifier,
    ) {
        val actionDefinition = properties[actionProp]
        val childId = properties[childProp] ?: return
        val currentAction by rememberUpdatedState(actionDefinition)
        val onClick: () -> Unit = remember {
            { currentAction?.let { dispatchAction(it) } }
        }

        Button(onClick = onClick, modifier = modifier) {
            val childState = observeA2uiComponentState(id = childId)
            when (childState) {
                is A2uiComponentState.Loading -> CircularProgressIndicator()
                is A2uiComponentState.Error -> Text("Error")
                is A2uiComponentState.Success -> A2uiComponent(childState.component)
            }
        }
    }
}

จัดการคอมโพเนนต์ย่อยและการแสดงผลแบบค่อยๆ

คอมโพเนนต์ที่รองรับองค์ประกอบย่อยที่ซ้อนกันจะใช้ observeA2uiComponentState(id) เพื่อ สังเกตสถานะขององค์ประกอบย่อย ซึ่งช่วยให้การแสดงผลแบบค่อยๆ เป็นค่อยๆ ไปทำงานได้ โดยที่คอนเทนเนอร์หลักจะแสดงผลเชลล์ในขณะที่คอมโพเนนต์ย่อยโหลดแยกกัน

val headerChildProp = A2uiProperty.componentId("headerId", required = true)

@Composable
fun A2uiComponentScope.CustomCompositeContent(
    properties: A2uiComponentProperties,
) {
    val headerId = properties[headerChildProp] ?: return

    val headerState = observeA2uiComponentState(id = headerId)
    when (headerState) {
        is A2uiComponentState.Loading -> {
            // Render a localized loading placeholder
            LinearProgressIndicator()
        }
        is A2uiComponentState.Error -> {
            // Render a localized error fallback
            Text("Failed to load header")
        }
        is A2uiComponentState.Success -> {
            // Forward the resolved child component to the visual UI router
            A2uiComponent(headerState.component)
        }
    }
}

หากต้องการจัดการคอลเล็กชันหรือรายการขององค์ประกอบย่อย (เช่น รายการในคอลัมน์ แถว หรือ รายการ) ให้ประกาศพร็อพเพอร์ตี้โดยใช้ A2uiProperty.childList และแก้ไข องค์ประกอบย่อยด้วย bindChildReferences ดังนี้

val childrenProp = A2uiProperty.childList("children", required = true)

@Composable
fun A2uiComponentScope.CustomColumn(
    properties: A2uiComponentProperties,
    modifier: Modifier = Modifier,
) {
    // Resolve child references (supports both static ID arrays and dynamic data templates)
    val childReferences = properties.bindChildReferences(childrenProp) ?: return

    Column(modifier = modifier) {
        childReferences.forEach { reference ->
            key(reference.id, reference.baseDataPath) {
                val childState = observeA2uiComponentState(reference)
                when (childState) {
                    is A2uiComponentState.Loading -> CircularProgressIndicator()
                    is A2uiComponentState.Error -> Text("Failed to load child")
                    is A2uiComponentState.Success -> A2uiComponent(childState.component)
                }
            }
        }
    }
}

ผสานรวมการแสดงผลสื่อแบบเนทีฟในแคตตาล็อกพื้นฐาน

เมื่อใช้การติดตั้งใช้งานแคตตาล็อกพื้นฐานที่ระบุไว้ (androidx.compose.material3:material3-a2ui) คุณจะเสียบไลบรารีสื่อที่ต้องการ (เช่น Coil สำหรับรูปภาพหรือ ExoPlayer สำหรับวิดีโอ) เข้ากับคอมโพเนนต์สื่อของแคตตาล็อกพื้นฐานได้โดยทำดังนี้

// Configure an Image component for the Basic Catalog using Coil
val coilImage = MaterialA2uiBasicCatalogV1Defaults.image { url, desc, scale, modifier, onError ->
    AsyncImage(
        model = url,
        contentDescription = desc,
        contentScale = scale,
        modifier = modifier,
        onError = { state -> onError(state.result.throwable) },
    )
}

รายละเอียดการติดตั้งใช้งาน

ส่วนต่อไปนี้จะอธิบายการปล่อย UI แบบเรียกซ้ำ การประเมินพร็อพเพอร์ตี้แบบไดนามิก และการรายงานข้อผิดพลาด

เส้นทางของผู้ใช้ในการติดตั้งใช้งานคอมโพเนนต์จะแนะนำ API หลักต่อไปนี้

  • A2uiComponent: อินเทอร์เฟซที่กำหนดข้อมูลเมตาของคอมโพเนนต์ สคีมาพร็อพเพอร์ตี้ การตรวจสอบความพร้อม (isReady) และการปล่อยการแสดงผล (Content)
  • A2uiProperty: การประกาศพร็อพเพอร์ตี้แบบกำหนดประเภทแบบคงที่ซึ่งใช้สำหรับสคีมา JSON และการสร้างค่ารันไทม์
  • A2uiComponentScope: ขอบเขตตัวรับที่ให้ความสามารถตามบริบท (เช่น การเชื่อมโยงข้อมูล การส่งการดำเนินการ และการสังเกตสถานะขององค์ประกอบย่อย) ไปยัง การติดตั้งใช้งานคอมโพเนนต์
  • A2uiComponentProperties: คอนเทนเนอร์สำหรับพร็อพเพอร์ตี้คอมโพเนนต์ที่ได้รับ จากเอเจนต์ที่ให้การเข้าถึงพร็อพเพอร์ตี้ที่ปลอดภัยตามประเภท
  • A2uiComponentState: แสดงถึงสถานะการโหลดแบบรีแอกทีฟ ความสำเร็จ หรือข้อผิดพลาด ของคอมโพเนนต์

การปล่อย UI แบบเรียกซ้ำและการกำหนดเส้นทางแบบไดนามิก

สถานะรูทที่ผู้เรียกใช้ย้ายขึ้น (หรือสถานะคอมโพเนนต์ย่อยที่แก้ไขภายในคอมโพเนนต์หลัก) จะเริ่มการแสดงผลคอมโพเนนต์แบบเรียกซ้ำผ่านฟังก์ชันที่ประกอบกันได้ A2uiComponent ฟังก์ชันนี้ทำหน้าที่เป็นเราเตอร์แบบไดนามิกแทนที่จะเชื่อมโยงสถานะที่แก้ไขแล้วกับการใช้งาน UI ที่เฉพาะเจาะจง