Skip to content

Commit 4c9c367

Browse files
feat(android): Fix rendering, stability, and UI components
Major improvements to the Android A2UI client to resolve blank screen issues and enhance UI rendering. Key Changes: - **Fix JSON-RPC**: Corrected method name to message/send and fixed serialization logic to handle polymorphic ServerPart correctly. - **Fix UI Rendering**: - Implemented IconRenderer with reflection to support all Material Icons dynamically. - Added DividerRenderer to support separator lines. - Integrated Coil for image loading and added localhost->10.0.2.2 URL rewriting for emulator. - **Stability**: - Increased OKHttp timeout to 90s. - Increased Gradle JVM heap size to 4GB to prevent OOM during build. - **Documentation**: - Updated Android client README with setup instructions. - Cleaned up build logs.
1 parent 4d0548d commit 4c9c367

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+3416
-395
lines changed

.idea/.name

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/deploymentTargetSelector.xml

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/kotlinc.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/migrations.xml

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ANDROID_GUIDE.md

Lines changed: 0 additions & 75 deletions
This file was deleted.

renderers/android/a2ui-compose/build.gradle.kts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
plugins {
22
alias(libs.plugins.android.library)
33
alias(libs.plugins.jetbrains.kotlin.android)
4+
alias(libs.plugins.kotlin.serialization)
45
}
56

67
android {
@@ -31,7 +32,7 @@ android {
3132
compose = true
3233
}
3334
composeOptions {
34-
kotlinCompilerExtensionVersion = "1.5.1"
35+
kotlinCompilerExtensionVersion = "1.5.11"
3536
}
3637
}
3738

@@ -46,6 +47,10 @@ dependencies {
4647
implementation(libs.androidx.ui.graphics)
4748
implementation(libs.androidx.ui.tooling.preview)
4849
implementation(libs.androidx.material3)
50+
implementation(libs.androidx.compose.material.icons.extended)
51+
implementation(libs.kotlinx.serialization.json)
52+
implementation("io.coil-kt:coil-compose:2.6.0")
53+
implementation(kotlin("reflect"))
4954

5055
testImplementation(libs.junit)
5156
androidTestImplementation(libs.androidx.junit)

renderers/android/a2ui-compose/src/main/kotlin/com/google/a2ui/compose/A2UIContext.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ package com.google.a2ui.compose
33
import com.google.a2ui.core.model.Action
44
import com.google.a2ui.core.model.BoundValue
55
import com.google.a2ui.core.state.SurfaceState
6+
import kotlinx.serialization.json.JsonElement
67

78
data class A2UIContext(
89
val state: SurfaceState,
9-
val onUserAction: (Action, String) -> Unit // action, sourceId
10+
val onUserAction: (Action, String, Map<String, JsonElement>) -> Unit // action, sourceId, context
1011
) {
1112
fun resolve(boundValue: BoundValue?): Any? {
1213
return state.resolve(boundValue)
@@ -19,4 +20,8 @@ data class A2UIContext(
1920
fun resolveBoolean(boundValue: BoundValue?): Boolean {
2021
return resolve(boundValue) as? Boolean ?: false
2122
}
23+
24+
fun resolveNumber(boundValue: BoundValue?): Double {
25+
return (resolve(boundValue) as? Number)?.toDouble() ?: 0.0
26+
}
2227
}

renderers/android/a2ui-compose/src/main/kotlin/com/google/a2ui/compose/A2UISurface.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.compose.runtime.CompositionLocalProvider
55
import androidx.compose.runtime.staticCompositionLocalOf
66
import com.google.a2ui.core.model.Action
77
import com.google.a2ui.core.state.SurfaceState
8+
import kotlinx.serialization.json.JsonElement
89

910
val LocalA2UIContext = staticCompositionLocalOf<A2UIContext> {
1011
error("No A2UIContext provided")
@@ -14,7 +15,7 @@ val LocalA2UIContext = staticCompositionLocalOf<A2UIContext> {
1415
fun A2UISurface(
1516
surfaceId: String,
1617
state: SurfaceState,
17-
onUserAction: (Action, String) -> Unit
18+
onUserAction: (Action, String, Map<String, JsonElement>) -> Unit
1819
) {
1920
val context = A2UIContext(
2021
state = state,

renderers/android/a2ui-compose/src/main/kotlin/com/google/a2ui/compose/ComponentRegistry.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import com.google.a2ui.compose.components.TabsRenderer
1414
import com.google.a2ui.compose.components.TextFieldRenderer
1515
import com.google.a2ui.compose.components.TextRenderer
1616
import com.google.a2ui.compose.components.VideoRenderer
17+
import com.google.a2ui.compose.components.IconRenderer
18+
import com.google.a2ui.compose.components.DividerRenderer
1719
import com.google.a2ui.core.model.ComponentWrapper
1820

1921
typealias ComponentRenderer = @Composable (ComponentWrapper, A2UIContext) -> Unit
@@ -38,6 +40,8 @@ object ComponentRegistry {
3840
register("Modal") { wrapper, ctx -> ModalRenderer(wrapper.Modal!!, ctx) }
3941
register("DateTimeInput") { wrapper, ctx -> DateTimeRenderer(wrapper.DateTimeInput!!, ctx) }
4042
register("Video") { wrapper, ctx -> VideoRenderer(wrapper.Video!!, ctx) }
43+
register("Icon") { wrapper, ctx -> IconRenderer.Render(wrapper, ctx) }
44+
register("Divider") { wrapper, ctx -> DividerRenderer(wrapper.Divider!!, ctx) }
4145
}
4246

4347
fun register(type: String, renderer: ComponentRenderer) {
@@ -68,7 +72,9 @@ object ComponentRegistry {
6872
wrapper.Modal != null -> "Modal"
6973
wrapper.DateTimeInput != null -> "DateTimeInput"
7074
wrapper.Video != null -> "Video"
71-
wrapper.Audio != null -> "Audio" // Audio maps to Video/Media renderer usually or separate
75+
wrapper.Audio != null -> "Audio"
76+
wrapper.Icon != null -> "Icon"
77+
wrapper.Divider != null -> "Divider"
7278
else -> null
7379
}
7480
}

renderers/android/a2ui-compose/src/main/kotlin/com/google/a2ui/compose/components/ButtonRenderer.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,18 @@ fun ButtonRenderer(
2727
// To fix this properly, I should pass the ID to the renderer.
2828
// But let's stick to current plan and fix registry separately.
2929
// I'll emit "unknown" for now and fix later.
30-
context.onUserAction(action, "unknown_source_id")
30+
context.onUserAction(action, "unknown_source_id", emptyMap())
3131
}
32+
Unit
3233
}
3334

3435
Button(onClick = onClick) {
3536
if (properties.label != null) {
3637
Text(text = context.resolveString(properties.label))
37-
} else if (properties.child != null) {
38-
A2UIComponent(id = properties.child, context = context)
38+
} else {
39+
properties.child?.let { childId ->
40+
A2UIComponent(id = childId, context = context)
41+
}
3942
}
4043
}
4144
}

0 commit comments

Comments
 (0)