{"slug": "my-coding-agent-could-render-one-android-layout-a-real-screen-needed-activity", "title": "My Coding Agent Could Render One Android Layout. A Real Screen Needed Activity, Fragment, RecyclerView and Overlays.", "summary": "A developer built android-ui-renderer-mcp, an MCP server that renders composed Android screens — an Activity shell, Fragment container, RecyclerView rows and loading overlays — from real app XML resources while skipping the Fragment runtime, DI, navigation and networking. A new render_target tool with the activity_fragment kind inflates the Activity and Fragment layouts separately and fills list rows and overlays from deterministic request fixtures, freezing indeterminate ProgressBars so screenshots stay reproducible for automated visual checks.", "body_md": "My coding agent could already render a single Android XML layout through Robolectric and get back a PNG plus a View Tree with pixel bounds. Then I pointed it at a real screen from a work app, and one layout stopped being enough.\n\nA real screen is an Activity shell, a Fragment container, a RecyclerView with data, and states like a loading overlay. Launching the production Fragment would drag in DI, navigation, networking and the rest of the app's runtime. I wanted something narrower: a screen real enough for visual checks, without running the app.\n\nThis is how [android-ui-renderer-mcp](https://github.com/hram/android-ui-renderer-mcp) got composed-screen rendering.\n\nThe work screen can't be shown, so every image here is a render of an open demo app in the same repository ([`sample/`](https://github.com/hram/android-ui-renderer-mcp/tree/main/sample)). It has the same structure — Activity with a toolbar, Fragment, RecyclerView, master-detail, loading overlay — and each image can be reproduced from the request stored next to it.\n\n```\nActivity XML          — real\nFragment XML          — real\nitem layout           — real\ndrawable              — real\n\nFragment runtime      — not run\nDI                    — not run\nnavigation            — not run\nnetwork               — not run\ndata                  — set explicitly\n```\n\nThat is the main design decision: the renderer uses the app's real UI resources, but takes runtime state from a deterministic request. Otherwise it becomes one more way to run the app, only harder than an emulator.\n\nA new `render_target` tool supports one target kind, `activity_fragment`. It inflates the Activity XML, finds the container, inflates the Fragment XML separately and inserts it. The production Fragment class never starts.\n\n```\n{\n  \"target\": {\n    \"kind\": \"activity_fragment\",\n    \"activityLayout\": \"activity_main\",\n    \"containerId\": \"fragmentContainer\",\n    \"fragmentLayout\": \"fragment_library\"\n  },\n  \"widthPx\": 1920,\n  \"heightPx\": 1200,\n  \"densityDpi\": 240,\n  \"orientation\": \"landscape\"\n}\n```\n\n*The toolbar title and menu icons come from XML (`app:title`, `app:menu`); no Activity code ran. The details on the right are an `<include>`, filled from the request.*\n\nI'm not emulating the Fragment lifecycle. I need its visual result from real resources, because the agent changes XML and has to see what ends up on screen.\n\nAn empty RecyclerView says little about the real UI, and running the production adapter would pull real data back in. So list data became part of the request: the real `itemLayout`, the orientation, the rows, and a fixture per row. The renderer creates a temporary adapter and inflates the real item XML for each row.\n\nOne row from the request above, the selected book:\n\n```\n{\n  \"@id/title\": { \"text\": \"Designing Data-Intensive Applications\" },\n  \"@id/cover\": { \"image\": { \"type\": \"drawable_resource\", \"value\": \"@drawable/cover_green\" } },\n  \"@id/statusBadge\": { \"text\": \"On loan\", \"backgroundDrawable\": \"@drawable/bg_badge_on_loan\", \"textColor\": \"#8A4B08\" },\n  \"@id/root\": { \"selected\": true, \"backgroundDrawable\": \"@drawable/bg_book_row_selected\" }\n}\n```\n\nThe UI structure is real; the data is test data and fully controlled. The renderer doesn't guess values from `tools:*`. If the agent wants three rows, it describes three rows.\n\nA loading overlay above Activity + Fragment is just another XML layout with its own fixture. The catch is the indeterminate ProgressBar: it animates, so two screenshots in a row can differ. For automated visual checks that's needless nondeterminism, so the renderer freezes it into a static ring before drawing.\n\nThis doesn't prove the animation works. It proves the indicator exists, sits in the right place and has the expected geometry, which is the level of realism this task needs.\n\nFor the work screen I also pinned down the real tablet profile: 1280×800 screen, 1280×728 app area (72 px at the bottom taken by system navigation), `densityDpi` 240, `fontScale` 1.0, `ru-RU`, day mode, landscape. For a screenshot that's a detail; for comparing bounds it isn't. The agent has to measure in the same area the real screen has.\n\nAt some point I opened a saved render and realized its PNG and View Tree were there, but the call that produced them wasn't: which tool, which arguments, which project root, module, variant and Gradle test task. A reproducible renderer with a non-reproducible run history.\n\nEvery sidecar run now also saves `request.json` (the normalized request) and `replay.json` (the recipe). For the overlay screen above, shortened:\n\n```\n{\n  \"format\": \"android-ui-renderer-mcp/replay/v1\",\n  \"tool\": \"render_target\",\n  \"arguments\": {\n    \"target\": { \"kind\": \"activity_fragment\", \"activityLayout\": \"activity_main\", \"containerId\": \"fragmentContainer\", \"fragmentLayout\": \"fragment_library\" },\n    \"widthPx\": 1920, \"heightPx\": 1200, \"densityDpi\": 240, \"orientation\": \"landscape\",\n    \"overlays\": [{ \"layout\": \"view_loading_overlay\", \"fixture\": { \"@id/loadingRoot\": { \"visibility\": \"visible\" } } }]\n  },\n  \"project\": { \"root\": \"sample\", \"module\": \":app\", \"variant\": \"debug\", \"testTask\": \":app:testDebugUnitTest\" }\n}\n```\n\nA render session is now a small experiment you can repeat on the same project revision and renderer version.\n\nI built the demo later, only to have images I could publish. It found real issues.\n\n**The renderer silently required JUnit.** The first demo render failed:\n\n```\nerror: package org.junit does not exist\n```\n\nThe probe is a JUnit 4 test, but the init script added only Robolectric. The work project had JUnit in its test dependencies, like any project from the default Android Studio template, so this never showed up. Now JUnit 4.13.2 is added for the render run only if the project doesn't declare it; a project on JUnit 4.12 stays on 4.12, checked against the test classpath.\n\n**A night-mode contrast bug in the demo itself.** An outlined button was almost invisible because the theme's primary color matched the dark toolbar. The `nightMode: true` render showed it; reading the XML didn't.\n\n**Byte-identical re-renders.** Re-running all seven demo scenarios on the same machine produced PNGs identical to the saved ones. With `replay.json`, a replay gives the same image, not a similar one.\n\n**The View Tree sees what the PNG hides.** A long title at `fontScale: 1.3`:\n\n```\n\"textLayout\": { \"textSizePx\": 40.0, \"maxLines\": 1, \"lineCount\": 1, \"ellipsisCount\": 81, \"truncated\": true }\n```\n\nThe bounds didn't change and the image looks fine, but 81 characters became an ellipsis. Also, 16sp at `fontScale: 1.3` is 40 px, not 41.6: since Android 14, large text scales non-linearly.\n\nI have a detailed worklog for this iteration.\n\n**Me:** picked the real screen as a testing ground and made the work app read-only; asked for RecyclerView support; supplied a mockup as the source of test data and the real tablet profile; stopped the agent before the overlay/dialog changes and asked for analysis first; chose a separate `render_target` over overloading `render_layout`; noticed that renders couldn't be replayed; checked the composed render with the overlay myself before allowing the commit. For the demo, I defined the screens and asked for JUnit to be added only when missing.\n\n**The coding agent:** studied the real screen's XML and the renderer's limits, proposed the deterministic RecyclerView adapter, implemented list and drawable-state support, the composed target, overlays and the replay recipe, ran checks on an isolated copy of the work project without changing it, and wrote the demo app, its render requests and the JUnit fix.\n\nI didn't type most of the code. I decided which part of reality the tool should model and what it must not do.\n\n`activity_fragment`: one container, one fragment layout. Master-detail in the demo is a single fragment layout, not two fragments.\nEach step of this project came down to one question: **which part of the real app must stay real, and which can be replaced with a deterministic model?** My current answer: resources and geometry stay real; data and runtime state are explicit and controlled; and every render leaves enough behind to be repeated.\n\nThe renderer and the demo are open source: [hram/android-ui-renderer-mcp](https://github.com/hram/android-ui-renderer-mcp). The full case study is on my site: [https://hram.github.io/en/articles/android-ui-renderer-composed-screens/](https://hram.github.io/en/articles/android-ui-renderer-composed-screens/)", "url": "https://wpnews.pro/news/my-coding-agent-could-render-one-android-layout-a-real-screen-needed-activity", "canonical_source": "https://dev.to/hram/my-coding-agent-could-render-one-android-layout-a-real-screen-needed-activity-fragment-2fp6", "published_at": "2026-09-29 20:32:52+00:00", "updated_at": "2026-09-29 20:46:47.155010+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "agent-protocols", "ai-tools"], "entities": ["android-ui-renderer-mcp", "Robolectric", "Android", "RecyclerView", "Fragment", "Activity"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/my-coding-agent-could-render-one-android-layout-a-real-screen-needed-activity", "markdown": "https://wpnews.pro/news/my-coding-agent-could-render-one-android-layout-a-real-screen-needed-activity.md", "text": "https://wpnews.pro/news/my-coding-agent-could-render-one-android-layout-a-real-screen-needed-activity.txt", "jsonld": "https://wpnews.pro/news/my-coding-agent-could-render-one-android-layout-a-real-screen-needed-activity.jsonld"}}