A2uiSurface

Functions summary

Unit
@Composable
A2uiSurface(
    surfaceModel: A2uiSurfaceModel,
    modifier: Modifier,
    loadingContent: @Composable () -> Unit,
    errorContent: @Composable (A2uiException) -> Unit,
    transitionSpec: (AnimatedContentTransitionScope<A2uiComponentState>.() -> ContentTransform)?
)

Displays an A2UI surface styled with Material Design 3.

Functions

@Composable
fun A2uiSurface(
    surfaceModel: A2uiSurfaceModel,
    modifier: Modifier = Modifier,
    loadingContent: @Composable () -> Unit = { A2uiSurfaceDefaults.LoadingIndicator() },
    errorContent: @Composable (A2uiException) -> Unit = { A2uiSurfaceDefaults.ErrorFallback(it) },
    transitionSpec: (AnimatedContentTransitionScope<A2uiComponentState>.() -> ContentTransform)? = A2uiSurfaceDefaults.transitionSpec
): Unit

Displays an A2UI surface styled with Material Design 3.

This composable acts as the visual root for an A2UI surface. It observes the reactive state of the root component within the provided surfaceModel and automatically handles transitions between loading, error, and success states. It applies Material 3 design patterns for its default loading indicator, error fallback, and animated transitions between layout changes.

Note that data-only updates to the underlying A2uiSurfaceModel (e.g., text or binding changes that do not alter the component hierarchy) do not trigger structural transition animations, ensuring high-performance reactive updates.

Setup and Initialization

To obtain a valid surfaceModel instance:

  1. Create an A2uiCatalog using the androidx.a2ui.compose.ui.A2uiCatalog or androidx.compose.material3.a2ui.catalog.materialA2uiBasicCatalogV1 factory functions.

  2. Create an androidx.a2ui.model.processor.A2uiMessageProcessor using the androidx.a2ui.compose.ui.A2uiMessageProcessor factory function with the catalog(s), typically hosted in a ViewModel.

  3. Run androidx.a2ui.model.processor.A2uiMessageProcessor.collectMessages on a background coroutine dispatcher to process agent messages.

  4. Collect androidx.a2ui.model.processor.A2uiMessageProcessor.activeSurfaces and pass the emitted A2uiSurfaceModel to this composable.

For basic A2uiSurface usage:

import androidx.a2ui.compose.ui.A2uiMessageProcessor
import androidx.a2ui.model.catalog.functions.A2uiLocaleProvider
import androidx.a2ui.model.protocol.A2uiComponentPayload
import androidx.a2ui.model.protocol.A2uiCreateSurfaceMessage
import androidx.a2ui.model.protocol.A2uiUpdateComponentsMessage
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.a2ui.A2uiSurface
import androidx.compose.material3.a2ui.catalog.MaterialA2uiBasicCatalogV1Defaults
import androidx.compose.material3.a2ui.catalog.materialA2uiBasicCatalogV1
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier

val catalog = remember {
    materialA2uiBasicCatalogV1(
        image = MaterialA2uiBasicCatalogV1Defaults.image { _, _, _, _, _ -> },
        video = MaterialA2uiBasicCatalogV1Defaults.video { _, _, _ -> },
        audioPlayer = MaterialA2uiBasicCatalogV1Defaults.audioPlayer { _, _, _, _ -> },
        urlOpener = {},
        messageFormatter = { pattern, _, _ -> pattern },
        localeProvider = A2uiLocaleProvider.Default,
    )
}
// Note: The message processor should typically be hosted in a ViewModel.
val processor = remember(catalog) { A2uiMessageProcessor(catalogs = listOf(catalog)) }

LaunchedEffect(processor) {
    // Note: Message collection should typically be run on a background thread.
    launch(Dispatchers.Default) { processor.collectMessages() }

    val surfaceId = "surface_1"
    // Simulate payloads received from an agent to create the surface and its root component
    processor.processMessage(A2uiCreateSurfaceMessage(surfaceId, catalog.id))
    processor.processMessage(
        A2uiUpdateComponentsMessage(
            surfaceId,
            listOf(
                A2uiComponentPayload(
                    id = "root",
                    type = "Text",
                    properties = mapOf("text" to "Hello, A2UI with Material 3!"),
                )
            ),
        )
    )
}

val surfaces by processor.activeSurfaces.collectAsState()
// Note: The UI is typically expected to render all active surfaces (e.g., in a list).
val surfaceModel = surfaces.firstOrNull()

if (surfaceModel != null) {
    A2uiSurface(surfaceModel = surfaceModel, modifier = Modifier.fillMaxSize())
}

To customize the loading indicator and error fallback content:

import androidx.a2ui.compose.ui.A2uiMessageProcessor
import androidx.a2ui.model.catalog.functions.A2uiLocaleProvider
import androidx.a2ui.model.protocol.A2uiComponentPayload
import androidx.a2ui.model.protocol.A2uiCreateSurfaceMessage
import androidx.a2ui.model.protocol.A2uiUpdateComponentsMessage
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.a2ui.A2uiSurface
import androidx.compose.material3.a2ui.catalog.MaterialA2uiBasicCatalogV1Defaults
import androidx.compose.material3.a2ui.catalog.materialA2uiBasicCatalogV1
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

val catalog = remember {
    materialA2uiBasicCatalogV1(
        image = MaterialA2uiBasicCatalogV1Defaults.image { _, _, _, _, _ -> },
        video = MaterialA2uiBasicCatalogV1Defaults.video { _, _, _ -> },
        audioPlayer = MaterialA2uiBasicCatalogV1Defaults.audioPlayer { _, _, _, _ -> },
        urlOpener = {},
        messageFormatter = { pattern, _, _ -> pattern },
        localeProvider = A2uiLocaleProvider.Default,
    )
}
// Note: The message processor should typically be hosted in a ViewModel.
val processor = remember(catalog) { A2uiMessageProcessor(catalogs = listOf(catalog)) }

LaunchedEffect(processor) {
    // Note: Message collection should typically be run on a background thread.
    launch(Dispatchers.Default) { processor.collectMessages() }

    val surfaceId = "surface_1"
    // Simulate payloads received from an agent to create the surface
    processor.processMessage(A2uiCreateSurfaceMessage(surfaceId, catalog.id))

    // Simulate network latency before components arrive so that the loading content is visible
    delay(2000.milliseconds)

    // Populate its root component
    processor.processMessage(
        A2uiUpdateComponentsMessage(
            surfaceId,
            listOf(
                A2uiComponentPayload(
                    id = "root",
                    type = "Text",
                    properties = mapOf("text" to "Hello, A2UI with Material 3!"),
                )
            ),
        )
    )
}

val surfaces by processor.activeSurfaces.collectAsState()
// Note: The UI is typically expected to render all active surfaces (e.g., in a list).
val surfaceModel = surfaces.firstOrNull()

if (surfaceModel != null) {
    A2uiSurface(
        surfaceModel = surfaceModel,
        modifier = Modifier.fillMaxSize(),
        loadingContent = {
            Box(
                modifier = Modifier.fillMaxWidth().padding(16.dp),
                contentAlignment = Alignment.Center,
            ) {
                LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
            }
        },
        errorContent = { exception ->
            Surface(
                color = MaterialTheme.colorScheme.errorContainer,
                shape = MaterialTheme.shapes.medium,
                modifier = Modifier.padding(16.dp),
            ) {
                Text(
                    text = "Failed to load surface: ${exception.message}",
                    modifier = Modifier.padding(16.dp),
                )
            }
        },
    )
}

To customize transition animations between visual states:

import androidx.a2ui.compose.ui.A2uiMessageProcessor
import androidx.a2ui.model.catalog.functions.A2uiLocaleProvider
import androidx.a2ui.model.protocol.A2uiComponentPayload
import androidx.a2ui.model.protocol.A2uiCreateSurfaceMessage
import androidx.a2ui.model.protocol.A2uiUpdateComponentsMessage
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.a2ui.A2uiSurface
import androidx.compose.material3.a2ui.catalog.MaterialA2uiBasicCatalogV1Defaults
import androidx.compose.material3.a2ui.catalog.materialA2uiBasicCatalogV1
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier

val catalog = remember {
    materialA2uiBasicCatalogV1(
        image = MaterialA2uiBasicCatalogV1Defaults.image { _, _, _, _, _ -> },
        video = MaterialA2uiBasicCatalogV1Defaults.video { _, _, _ -> },
        audioPlayer = MaterialA2uiBasicCatalogV1Defaults.audioPlayer { _, _, _, _ -> },
        urlOpener = {},
        messageFormatter = { pattern, _, _ -> pattern },
        localeProvider = A2uiLocaleProvider.Default,
    )
}
// Note: The message processor should typically be hosted in a ViewModel.
val processor = remember(catalog) { A2uiMessageProcessor(catalogs = listOf(catalog)) }

LaunchedEffect(processor) {
    // Note: Message collection should typically be run on a background thread.
    launch(Dispatchers.Default) { processor.collectMessages() }

    val surfaceId = "surface_1"
    // Simulate payloads received from an agent to create the surface
    processor.processMessage(A2uiCreateSurfaceMessage(surfaceId, catalog.id))

    // Simulate network latency before components arrive to demonstrate the transition animation
    delay(2000.milliseconds)

    // Populate its root component
    processor.processMessage(
        A2uiUpdateComponentsMessage(
            surfaceId,
            listOf(
                A2uiComponentPayload(
                    id = "root",
                    type = "Text",
                    properties = mapOf("text" to "Hello, A2UI with Material 3!"),
                )
            ),
        )
    )
}

val surfaces by processor.activeSurfaces.collectAsState()
// Note: The UI is typically expected to render all active surfaces (e.g., in a list).
val surfaceModel = surfaces.firstOrNull()

if (surfaceModel != null) {
    A2uiSurface(
        surfaceModel = surfaceModel,
        modifier = Modifier.fillMaxSize(),
        transitionSpec = {
            (fadeIn(animationSpec = tween(600)) togetherWith
                    fadeOut(animationSpec = tween(600)))
                .using(SizeTransform(clip = false))
        },
    )
}
Parameters
surfaceModel: A2uiSurfaceModel

the A2uiSurfaceModel containing the data, components, and catalog for this UI, typically obtained from androidx.a2ui.model.processor.A2uiMessageProcessor.activeSurfaces

modifier: Modifier = Modifier

the Modifier to be applied to the surface layout

loadingContent: @Composable () -> Unit = { A2uiSurfaceDefaults.LoadingIndicator() }

the composable to display while the root component is loading or resolving its dynamic data bindings. By default, this uses A2uiSurfaceDefaults.LoadingIndicator

errorContent: @Composable (A2uiException) -> Unit = { A2uiSurfaceDefaults.ErrorFallback(it) }

the composable to display if the root component fails to evaluate or render due to a validation or runtime error. By default, this uses A2uiSurfaceDefaults.ErrorFallback

transitionSpec: (AnimatedContentTransitionScope<A2uiComponentState>.() -> ContentTransform)? = A2uiSurfaceDefaults.transitionSpec

the ContentTransform animation used when the root transitions between loading, error, and success states. By default, uses A2uiSurfaceDefaults.transitionSpec. Set to null to disable animations

Throws
IllegalArgumentException

if surfaceModel does not implement A2uiCoreSurfaceModel (e.g., if the message processor was not created using the androidx.a2ui.compose.ui.A2uiMessageProcessor factory function), or if its catalog does not implement A2uiCatalog (e.g., if the catalog was not created using the androidx.a2ui.compose.ui.A2uiCatalog factory function)