Na arquitetura A2UI, cada superfície é impulsionada por um catálogo de componentes. Em vez de um agente de IA inventar suas próprias primitivas de interface ou gerar código arbitrário, seu catálogo declara os componentes, esquemas de propriedades e recursos disponíveis para o agente. Em seguida, o agente usa esses componentes para construir uma interface do usuário.
Ao criar um catálogo personalizado para o sistema de design do seu app, você
implementa componentes que mapeiam essas definições do catálogo em elementos concretos da interface do Jetpack
Compose. Cada componente da A2UI (A2uiComponent) define o contrato de esquema de propriedades, avalia a prontidão à medida que os dados dinâmicos chegam, vincula propriedades reativas do modelo de dados, emite a interface do Compose e envia ações de interação do usuário de volta ao agente.
O renderizador de interface do Compose (androidx.a2ui.compose:compose-ui) fornece as interfaces e os escopos de receptor necessários para implementar componentes personalizados que seguem o sistema de design do app.
Declarar propriedades de componentes com tipagem estática
Antes da renderização, declare as propriedades que um componente espera do
agente. A camada de execução fornece APIs A2uiProperty com tipagem estática usadas
para geração de esquema JSON e extração de valores no ambiente de execução:
// 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)
Implementar a interface A2uiComponent
Implemente a interface A2uiComponent para definir o esquema de um componente e mapear
propriedades recebidas do agente na interface do Compose:
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,
)
}
}
Resolver vinculações de modelo de dados regulares e bidirecionais
As implementações de componentes usam A2uiComponentScope para resolver propriedades
vinculadas dinamicamente. Para propriedades dinâmicas regulares, bind retorna o valor atual e se inscreve automaticamente nas atualizações do modelo de dados.
Para componentes de entrada interativos, bindUpdater retorna uma lambda de atualização estável.
Se o agente forneceu uma string literal em vez de um caminho de dados gravável, a lambda do
updater será null, indicando que o campo é somente leitura:
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)
}
}
Enviar ações do usuário para o agente
Os componentes interativos usam A2uiComponentScope.dispatchAction para enviar eventos do usuário de volta ao agente:
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)
}
}
}
}
Processar componentes filhos e renderização progressiva
Os componentes que oferecem suporte a filhos aninhados usam observeA2uiComponentState(id) para
observar os estados dos filhos. Isso permite a renderização progressiva, em que um contêiner
pai renderiza o shell enquanto os componentes filhos são carregados de forma independente:
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)
}
}
}
Para processar coleções ou listas de filhos (como itens em uma coluna, linha ou
lista), declare uma propriedade usando A2uiProperty.childList e resolva os
filhos com 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)
}
}
}
}
}
Integrar a renderização de mídia nativa no catálogo básico
Ao usar a implementação do catálogo básico fornecida
(androidx.compose.material3:material3-a2ui), você pode conectar suas bibliotecas de mídia preferidas (como Coil para imagens ou ExoPlayer para vídeo) aos componentes de mídia do catálogo
básico:
// 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) },
)
}
Detalhes de implementação
As seções a seguir explicam a emissão recursiva da interface, a avaliação dinâmica de propriedades e a geração de relatórios de erros.
As jornadas do usuário de implementação de componentes apresentam as seguintes APIs principais:
A2uiComponent: interface que define metadados de componentes, esquemas de propriedades, verificações de prontidão (isReady) e emissão de renderização (Content).A2uiProperty: uma declaração de propriedade com tipo estático usada para geração de esquema JSON e resolução de valor de tempo de execução.A2uiComponentScope: um escopo de receptor que fornece recursos contextuais (como vinculação de dados, envio de ações e observação do estado filho) para implementações de componentes.A2uiComponentProperties: um contêiner para propriedades de componentes recebidas do agente que fornece acesso a propriedades com segurança de tipo.A2uiComponentState: representa o estado de carregamento reativo, sucesso ou resolução de erro de um componente.
Emissão recursiva da interface e roteamento dinâmico
O estado raiz elevado pelo caller (ou estado do componente filho resolvido em um
elemento pai) inicia a renderização recursiva de componentes pela função combinável
A2uiComponent. Em vez de acoplar o estado resolvido a uma implementação de UI específica, essa função atua como um roteador dinâmico.