A2UI के कस्टम कॉम्पोनेंट लागू करना

A2UI आर्किटेक्चर में, हर सरफेस को कॉम्पोनेंट कैटलॉग से कंट्रोल किया जाता है. एआई एजेंट को अपने यूज़र इंटरफ़ेस (यूआई) प्रिमिटिव बनाने या मनमाना कोड जनरेट करने के बजाय, आपका कैटलॉग एजेंट के लिए उपलब्ध कॉम्पोनेंट, प्रॉपर्टी स्कीमा, और क्षमताओं के बारे में बताता है. इसके बाद, एजेंट इन कॉम्पोनेंट का इस्तेमाल करके, यूज़र इंटरफ़ेस (यूआई) बनाता है.

अपने ऐप्लिकेशन के डिज़ाइन सिस्टम के लिए कस्टम कैटलॉग बनाते समय, आपको ऐसे कॉम्पोनेंट लागू करने होते हैं जो कैटलॉग की परिभाषाओं को Jetpack Compose के यूज़र इंटरफ़ेस (यूआई) के एलिमेंट में मैप करते हैं. हर A2UI कॉम्पोनेंट (A2uiComponent) अपनी प्रॉपर्टी के स्कीमा कॉन्ट्रैक्ट को तय करता है. साथ ही, डाइनैमिक डेटा मिलने पर, यह तय करता है कि कॉम्पोनेंट इस्तेमाल के लिए तैयार है या नहीं. इसके अलावा, यह डेटा मॉडल से रिएक्टिव प्रॉपर्टी को बाइंड करता है, कंपोज़ यूज़र इंटरफ़ेस (यूआई) को दिखाता है, और उपयोगकर्ता के इंटरैक्शन से जुड़ी कार्रवाइयों को वापस एजेंट को भेजता है.

Compose UI रेंडरर (androidx.a2ui.compose:compose-ui) कस्टम कॉम्पोनेंट लागू करने के लिए ज़रूरी इंटरफ़ेस और रिसीवर स्कोप उपलब्ध कराता है. ये कॉम्पोनेंट, आपके ऐप्लिकेशन के डिज़ाइन सिस्टम के मुताबिक होते हैं.

स्टैटिक टाइप वाली कॉम्पोनेंट प्रॉपर्टी के बारे में जानकारी देना

रेंडर करने से पहले, उन प्रॉपर्टी के बारे में बताएं जिनकी ज़रूरत कॉम्पोनेंट को एजेंट से होती है. रनटाइम लेयर, स्टैटिक टाइप वाले A2uiProperty एपीआई उपलब्ध कराती है. इनका इस्तेमाल, 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 इंटरफ़ेस लागू करना

किसी कॉम्पोनेंट के स्कीमा को तय करने और एजेंट से मिली प्रॉपर्टी को Compose यूज़र इंटरफ़ेस (यूआई) पर मैप करने के लिए, A2uiComponent इंटरफ़ेस लागू करें:

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,
        )
    }
}

डेटा मॉडल की रेगुलर और टू-वे बाइंडिंग की समस्याओं को हल करना

कॉम्पोनेंट लागू करने के लिए, A2uiComponentScope का इस्तेमाल किया जाता है. इससे डाइनैमिक तौर पर बाइंड की गई प्रॉपर्टी को हल किया जा सकता है. डाइनैमिक प्रॉपर्टी के लिए, bind मौजूदा वैल्यू दिखाता है. साथ ही, डेटा मॉडल के अपडेट के लिए अपने-आप सदस्यता लेता है.

इंटरैक्टिव इनपुट कॉम्पोनेंट के लिए, bindUpdater एक स्टेबल अपडेटर लैम्डा दिखाता है. अगर एजेंट ने लिखने लायक डेटा पाथ के बजाय लिटरल स्ट्रिंग दी है, तो अपडेटर लैम्डा 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)
    }
}

उपयोगकर्ता की कार्रवाइयों को एजेंट को भेजना

इंटरैक्टिव कॉम्पोनेंट, उपयोगकर्ता के इवेंट को एजेंट को वापस भेजने के लिए A2uiComponentScope.dispatchAction का इस्तेमाल करते हैं:

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) },
    )
}

लागू करने से जुड़ी जानकारी

इन सेक्शन में, बार-बार यूज़र इंटरफ़ेस (यूआई) से जुड़ी जानकारी देने, डाइनैमिक प्रॉपर्टी का आकलन करने, और गड़बड़ी की रिपोर्टिंग के बारे में बताया गया है.

कॉम्पोनेंट लागू करने के लिए, उपयोगकर्ता के सफ़र में इन मुख्य एपीआई के बारे में बताया गया है:

  • A2uiComponent: यह इंटरफ़ेस, कॉम्पोनेंट के मेटाडेटा, प्रॉपर्टी स्कीमा, तैयारी की जांच (isReady), और रेंडरिंग उत्सर्जन (Content) को तय करता है.
  • A2uiProperty: यह स्टैटिक टाइप वाली प्रॉपर्टी का एलान है. इसका इस्तेमाल JSON स्कीमा जनरेट करने और रनटाइम वैल्यू रिज़ॉल्यूशन के लिए किया जाता है.
  • A2uiComponentScope: यह एक रिसीवर स्कोप है. यह कॉम्पोनेंट लागू करने के लिए, कॉन्टेक्स्ट से जुड़ी सुविधाएं उपलब्ध कराता है. जैसे, डेटा बाइंडिंग, ऐक्शन डिस्पैचिंग, और चाइल्ड स्टेट ऑब्ज़र्वेशन.
  • A2uiComponentProperties: यह एजेंट से मिली कॉम्पोनेंट प्रॉपर्टी के लिए कंटेनर है. यह टाइप-सेफ़ प्रॉपर्टी ऐक्सेस देता है.
  • A2uiComponentState: यह किसी कॉम्पोनेंट की रीऐक्टिव लोडिंग, सफलता या गड़बड़ी ठीक होने की स्थिति को दिखाता है.

रिकर्सिव यूज़र इंटरफ़ेस (यूआई) उत्सर्जन और डाइनैमिक राउटिंग

कॉलर (या पैरंट कॉम्पोनेंट में हल की गई चाइल्ड कॉम्पोनेंट की स्थिति) से रूट स्टेट को ऊपर ले जाने पर, A2uiComponent कंपोज़ेबल फ़ंक्शन के ज़रिए कॉम्पोनेंट को बार-बार रेंडर करना शुरू हो जाता है. इस फ़ंक्शन को किसी यूज़र इंटरफ़ेस (यूआई) के साथ जोड़ने के बजाय, इसे डाइनैमिक राउटर के तौर पर इस्तेमाल किया जाता है.