Dans l'architecture A2UI, chaque surface est pilotée par un catalogue de composants. Plutôt que de laisser un agent d'IA inventer ses propres primitives d'UI ou générer du code arbitraire, votre catalogue déclare les composants, les schémas de propriétés et les capacités disponibles pour l'agent. L'agent utilise ensuite ces composants pour construire une interface utilisateur.
Lorsque vous créez un catalogue personnalisé pour le système de conception de votre application, vous implémentez des composants qui mappent ces définitions de catalogue dans des éléments d'UI Jetpack Compose concrets. Chaque composant A2UI (A2uiComponent) définit son contrat de schéma de propriété, évalue l'état de préparation à l'arrivée des données dynamiques, lie les propriétés réactives du modèle de données, émet l'UI Compose et renvoie les actions d'interaction utilisateur à l'agent.
Le moteur de rendu de l'interface utilisateur Compose (androidx.a2ui.compose:compose-ui) fournit les interfaces et les portées du récepteur nécessaires à l'implémentation de composants personnalisés, qui suivent le système de conception de votre application.
Déclarer les propriétés des composants typés de manière statique
Avant le rendu, déclarez les propriétés qu'un composant attend de l'agent. La couche d'exécution fournit des API A2uiProperty à typage statique utilisées à la fois pour la génération de schémas JSON et pour l'extraction de valeurs au moment de l'exécution :
// 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)
Implémenter l'interface A2uiComponent
Implémentez l'interface A2uiComponent pour définir le schéma d'un composant et mapper les propriétés reçues de l'agent sur 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,
)
}
}
Résoudre les liaisons de modèle de données régulières et bidirectionnelles
Les implémentations de composants utilisent A2uiComponentScope pour résoudre les propriétés liées dynamiquement. Pour les propriétés dynamiques régulières, bind renvoie la valeur actuelle et s'abonne automatiquement aux mises à jour du modèle de données.
Pour les composants d'entrée interactifs, bindUpdater renvoie un lambda de mise à jour stable.
Si l'agent a fourni une chaîne littérale au lieu d'un chemin d'accès aux données modifiable, le lambda de mise à jour est null, ce qui indique que le champ est en lecture seule :
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)
}
}
Envoyer les actions de l'utilisateur à l'agent
Les composants interactifs utilisent A2uiComponentScope.dispatchAction pour renvoyer les événements utilisateur à l'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)
}
}
}
}
Gérer les composants enfants et le rendu progressif
Les composants acceptant les enfants imbriqués utilisent observeA2uiComponentState(id) pour observer les états des enfants. Cela permet un rendu progressif où un conteneur parent affiche son shell pendant que les composants enfants se chargent indépendamment :
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)
}
}
}
Pour gérer des collections ou des listes d'enfants (tels que des éléments dans une colonne, une ligne ou une liste), déclarez une propriété à l'aide de A2uiProperty.childList et résolvez les enfants avec 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)
}
}
}
}
}
Intégrer le rendu des contenus multimédias natifs dans le catalogue de base
Lorsque vous utilisez l'implémentation de catalogue de base fournie (androidx.compose.material3:material3-a2ui), vous pouvez brancher vos bibliothèques multimédias préférées (telles que Coil pour les images ou ExoPlayer pour les vidéos) dans les composants multimédias du catalogue de base :
// 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) },
)
}
Détails de mise en œuvre
Les sections suivantes expliquent l'émission récursive d'UI, l'évaluation dynamique des propriétés et le signalement des erreurs.
Les parcours utilisateur d'implémentation de composants présentent les API clés suivantes :
A2uiComponent: interface définissant les métadonnées des composants, les schémas de propriétés, les vérifications de disponibilité (isReady) et l'émission de rendu (Content).A2uiProperty: déclaration de propriété à typage statique utilisée pour la génération de schémas JSON et la résolution des valeurs d'exécution.A2uiComponentScope: portée du récepteur fournissant des fonctionnalités contextuelles (telles que la liaison de données, l'envoi d'actions et l'observation de l'état enfant) aux implémentations de composants.A2uiComponentProperties: conteneur pour les propriétés de composants reçues de l'agent qui fournit un accès aux propriétés avec sécurité du type.A2uiComponentState: représente l'état de chargement réactif, de réussite ou d'erreur d'un composant.
Émission récursive de l'UI et routage dynamique
L'état racine hissé par l'appelant (ou l'état du composant enfant résolu dans un parent) déclenche le rendu récursif des composants via la fonction composable A2uiComponent. Plutôt que de coupler étroitement l'état résolu à une implémentation d'UI spécifique, cette fonction agit comme un routeur dynamique.