Skip to content

ADFA-3874 | Fix radio group ID generation and refactor widget rendering#1271

Open
jatezzz wants to merge 2 commits intostagefrom
fix/ADFA-3874-radio-group-id-experimental
Open

ADFA-3874 | Fix radio group ID generation and refactor widget rendering#1271
jatezzz wants to merge 2 commits intostagefrom
fix/ADFA-3874-radio-group-id-experimental

Conversation

@jatezzz
Copy link
Copy Markdown
Collaborator

@jatezzz jatezzz commented May 4, 2026

This PR fixes the issue where individual widgets within a radio button group generated incorrect IDs (e.g., rb_group_textste_l6sp). To solve this and improve layout rendering, the widget creation logic has been extracted from LayoutRenderer into a dedicated WidgetFactory.

Additionally, this PR includes:

  • Widget Hierarchy Refactoring: Updated AndroidWidget to support child rendering via AndroidViewGroup, making it easier to render composite layouts like RadioGroup and LinearLayout (via HorizontalRowWidget).
  • Constants Extraction: Introduced AndroidConstants and AndroidWidgetTags to centralize standard Android XML strings and widget names.
  • Grammar Interfaces: Refactored WidgetGrammar into a robust interface hierarchy (LayoutGrammar, TextGrammar, CompoundButtonGrammar) and added grammars for RadioButton, CheckBox, Switch, and RadioGroup.
  • Validators: Added BooleanValidator and SpDimensionRangeValidator for stricter attribute validation.
  • OCR Text Cleaning: Improved TextCleaner with targeted regexes to correctly strip leading standalone circles and duplicated marker artifacts, and fixed dimension parsing to accurately extract sp values.

Details

  • Logic-related changes: LayoutRenderer has been vastly simplified and now delegates creation to WidgetFactory. The rendering pipeline inside AndroidWidget was split into resolveWidgetId, assembleAttributes, and writeXml to safely handle hierarchical attributes and default dimensions. View group attributes (like android:checkedButton and orientation margins) are now evaluated directly inside the new factory.
document_5172673150319593366.mp4

Ticket

ADFA-3874

Observation

The shift to an object-oriented composite pattern for widgets (AndroidViewGroup) and the introduction of AndroidConstants significantly cleans up the XML generation pipeline. This paves the way for adding more complex nested layouts (like ConstraintLayout or complex cards) in the future without bloating the renderer.

Refactor AndroidWidget to support nested view groups and add grammars for compound buttons.
@jatezzz jatezzz requested review from a team, Daniel-ADFA and avestaadfa May 4, 2026 20:24
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e2516a83-4155-40c7-a340-f3c9d69da891

📥 Commits

Reviewing files that changed from the base of the PR and between 808a214 and e57bb6e.

📒 Files selected for processing (2)
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/grammar/AttributeValidator.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/xml/WidgetFactory.kt
✅ Files skipped from review due to trivial changes (1)
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/xml/WidgetFactory.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/grammar/AttributeValidator.kt

📝 Walkthrough
  • Fixed radio button group ID generation and related regex: resolves malformed IDs for radio-group items (e.g., previously produced IDs like rb_group_textste_l6sp); resolveRadioGroupId regex updated to accept decimal values and additional patterns.
  • Introduced WidgetFactory to centralize widget creation and evaluate view-group attributes (e.g., android:checkedButton, orientation, margins); LayoutRenderer now delegates widget creation to the factory.
  • Refactored AndroidWidget rendering pipeline into resolveWidgetId, assembleAttributes, and writeXml steps; added support for composite patterns via AndroidViewGroup.
  • Added new widget hierarchy classes: AndroidViewGroup, HorizontalRowWidget, RadioGroupWidget to better model container widgets and nested layouts.
  • Expanded grammar system: refactored WidgetGrammar into a hierarchy (LayoutGrammar, TextGrammar, CompoundButtonGrammar) and added grammars for RadioButton, CheckBox, Switch, and RadioGroup to improve attribute validation.
  • Added AndroidConstants and AndroidWidgetTags singletons to centralize Android XML strings and widget tag names.
  • Introduced validators: BooleanValidator (fuzzy boolean matching) and SpDimensionRangeValidator (accepts "sp" including decimals and enforces min/max SP ranges).
  • Improved OCR/text cleaning: TextCleaner now uses targeted regexes to strip leading standalone circles, duplicated marker artifacts, and corrected SP dimension parsing in cleanSpDimension to better normalize sp units.
  • Checkbox-group handling: WidgetFactory computes/normalizes base checkbox group IDs (cb_group_*) and assigns alphabetic-suffixed IDs to children; radio-group handling propagates shared attributes and determines checked child ID.
  • Asset included: PR references an attached user-attachment asset (see PR attachments).

Risks & Best-practice violations:

  • Mutable widget state: AndroidWidget now exposes mutable properties (idOverride, extraAttrs) rather than immutable construction, increasing risk of state-related bugs and complicating testing and concurrency; consider immutability or a builder pattern.
  • API/behavioral breaking changes: Constructor and method signatures changed (AndroidWidget now accepts nullable ScaledBox?, render() signature simplified); callers must be updated or will face compilation/runtime issues.
  • Nullable ScaledBox: Changing box to nullable increases chance of null-related errors if callers or downstream code do not handle null boxes correctly.
  • Validator tightening: Replacing DimensionValidator with SpDimensionRangeValidator can reject previously-accepted dimension values if configured ranges are too strict; validate ranges against existing datasets.
  • ID-generation edge cases: Though regex was improved, ID normalization logic is complex—verify across diverse labels/OCR outputs to avoid regressions (especially for non-ASCII characters and decimal SP values).
  • Migration scope: LayoutRenderer no longer contains per-widget rendering helpers—ensure no remaining call sites expect the old behavior; thorough integration testing required.

Walkthrough

This PR refactors the Android UI rendering pipeline: introduces a stateful AndroidWidget hierarchy and container widgets, centralizes LayoutItem→AndroidWidget conversion in WidgetFactory, introduces Android XML constants, restructures grammar/attribute validators with inheritance, and updates text/dimension cleaning logic.

Changes

Unified Widget & Grammar Refactoring

Layer / File(s) Summary
Constants & Tags
cv-image-to-xml/src/main/java/.../xml/AndroidConstants.kt
Adds AndroidConstants and AndroidWidgetTags for common layout values and widget tag names.
Widget Base & Rendering
cv-image-to-xml/src/main/java/.../xml/AndroidWidget.kt
Reworks AndroidWidget into a stateful sealed class (nullable box), adds mutable idOverride/extraAttrs, splits render into id/attribute assembly, switches tag selection to AndroidWidgetTags, and adds view-group subclasses (AndroidViewGroup, HorizontalRowWidget, RadioGroupWidget).
Factory: LayoutItem → Widgets
cv-image-to-xml/src/main/java/.../xml/WidgetFactory.kt
Adds WidgetFactory(context, annotations) and createWidgets(item) to produce AndroidWidget lists for SimpleView, HorizontalRow, RadioGroup, and CheckboxGroup; handles id normalization, child id generation, checked state propagation, and horizontal-gap margin insertion.
Renderer Delegation
cv-image-to-xml/src/main/java/.../xml/LayoutRenderer.kt
LayoutRenderer now delegates to WidgetFactory.createWidgets(item) and renders returned widgets, removing prior inline type-specific rendering logic.
Grammar & Validators
cv-image-to-xml/src/main/java/.../grammar/WidgetGrammar.kt, AttributeValidator.kt, UiGrammarValidator.kt
WidgetGrammar.attributes now has a default and layered interfaces (LayoutGrammar, TextGrammar, CompoundButtonGrammar) so grammars extend via super.attributes + .... Adds BooleanValidator; removes DimensionValidator and adds SpDimensionRangeValidator(minSp,maxSp). Registry expanded to include RadioButtonGrammar, CheckBoxGrammar, SwitchGrammar, RadioGroupGrammar.
Parsing & Text Cleaning
cv-image-to-xml/src/main/java/.../FuzzyAttributeParser.kt, .../utils/TextCleaner.kt
cleanSpDimension now strips trailing `(sp

Sequence Diagram

sequenceDiagram
    participant LR as LayoutRenderer
    participant WF as WidgetFactory
    participant AW as AndroidWidget
    participant XC as XmlContext

    LR->>WF: createWidgets(item)
    alt SimpleView
        WF->>AW: createSimpleWidget(box)
        AW->>XC: resolveId()/assembleAttributes()
    else HorizontalRow
        WF->>WF: createHorizontalRow(boxes)
        loop per child
            WF->>AW: createSimpleWidget(box)
            AW->>AW: assembleAttributes()
        end
        WF->>AW: HorizontalRowWidget(children)
    else RadioGroup/CheckboxGroup
        WF->>WF: createRadioGroup/createCheckboxGroup(boxes)
        WF->>XC: resolveRadioGroupId()/resolveBaseId()
        loop per child
            WF->>AW: createSimpleWidget(child)
            AW->>AW: apply group attrs / set android:checked
        end
        WF->>AW: RadioGroupWidget(children, checkedId)
    end
    WF-->>LR: List<AndroidWidget>
    loop render each widget
        LR->>AW: render(context, indent)
        AW->>AW: assembleAttributes()
        AW->>AW: getChildren()
        alt has children
            AW->>AW: render child.render()
        else leaf
            AW->>AW: self-close tag
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • hal-eisen-adfa
  • itsaky-adfa
  • avestaadfa
  • Daniel-ADFA

"I hop and munched the code today,
Widgets nested in a tidy play,
Tags and grammars all aligned,
IDs and margins neatly assigned,
A rabbit cheers this render ballet 🐇"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and accurately summarizes the primary changes: fixing radio group ID generation and refactoring widget rendering into a dedicated WidgetFactory, which are the core objectives of the PR.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the radio group ID fix, WidgetFactory extraction, widget hierarchy refactoring, grammar improvements, and other relevant modifications across multiple files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ADFA-3874-radio-group-id-experimental

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/utils/TextCleaner.kt (1)

27-27: ⚡ Quick win

Reuse leadingMarkerRegex here to eliminate the duplicated inline literal.

The inline pattern on this line is byte-for-byte identical to leadingMarkerRegex introduced in this same PR. Leaving it inlined means a future change to leadingMarkerRegex silently diverges from cleanTextPreservingLeadingO.

♻️ Proposed fix
-        .replace(Regex("^[\\[\\]()●○□☑✓-]+\\s*"), "")
+        .replace(leadingMarkerRegex, "")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/utils/TextCleaner.kt`
at line 27, The inline Regex pattern
".replace(Regex(\"^[\\\\[\\\\]()●○□☑✓-]+\\\\s*\"), \"\")" in
cleanTextPreservingLeadingO duplicates the existing leadingMarkerRegex; replace
the inline Regex call with a reuse of the leadingMarkerRegex constant (e.g.,
.replace(leadingMarkerRegex, "")) so future changes to leadingMarkerRegex apply
consistently across the function.
cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/xml/WidgetFactory.kt (1)

119-125: 💤 Low value

Consider skipping zero-gap margins.

getMarginEndForHorizontalGap always returns a margin attribute, even when the gap is 0. Emitting android:layout_marginEnd="0dp" is harmless but adds noise to the generated XML.

Skip zero margins
 private fun getMarginEndForHorizontalGap(boxes: List<ScaledBox>, currentIndex: Int): Map<String, String> {
     if (currentIndex >= boxes.lastIndex) return emptyMap()
     val currentBox = boxes[currentIndex]
     val nextBox = boxes[currentIndex + 1]
     val gap = maxOf(0, nextBox.x - (currentBox.x + currentBox.w))
+    if (gap == 0) return emptyMap()
     return mapOf("android:layout_marginEnd" to "${gap}dp")
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/xml/WidgetFactory.kt`
around lines 119 - 125, The getMarginEndForHorizontalGap function always emits
an android:layout_marginEnd attribute even when gap is 0; change the logic so
after computing gap = maxOf(0, nextBox.x - (currentBox.x + currentBox.w)) you
return emptyMap() when gap == 0 and only return mapOf("android:layout_marginEnd"
to "${gap}dp") when gap > 0, keeping the early bounds check on currentIndex and
using the existing variables (boxes, currentIndex, currentBox, nextBox, gap) to
locate where to add this conditional.
cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/xml/AndroidWidget.kt (1)

11-12: 💤 Low value

Mutable state on sealed class instances.

Using var properties (idOverride, extraAttrs) makes widgets stateful. This works if widgets are created fresh per render (which appears to be the case in WidgetFactory), but could cause issues if widgets are cached or re-rendered.

This is acceptable given the current usage pattern, but worth documenting as an invariant.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/xml/AndroidWidget.kt`
around lines 11 - 12, The sealed class AndroidWidget exposes mutable properties
idOverride and extraAttrs which makes instances stateful; either make these
properties immutable (convert to constructor params or vals) or, if you must
keep them mutable, add a clear documentation comment on AndroidWidget explaining
the invariant that WidgetFactory must create fresh widget instances for each
render and widgets must never be cached/reused; alternatively provide an
explicit copy/withIdOverride/withExtraAttrs factory method on AndroidWidget so
callers create modified copies instead of mutating idOverride/extraAttrs
directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/grammar/AttributeValidator.kt`:
- Around line 39-51: SpDimensionRangeValidator currently only accepts integer
SPs because spRegex is "^(\\d+)sp$" and validate uses toIntOrNull; change the
regex (spRegex) to accept optional decimal parts (e.g.
"^(\\d+(?:\\.\\d+)?)sp$"), parse the captured group with toDoubleOrNull instead
of toIntOrNull, and compare the parsed Double against the minSp/maxSp bounds by
converting them to Double (e.g. value in minSp.toDouble()..maxSp.toDouble());
return trimmed when in range and null otherwise, leaving the class name and
validate method intact.

In
`@cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/xml/WidgetFactory.kt`:
- Around line 127-137: The normalization in resolveRadioGroupId is too broad
(the second when-branch uses startsWith("r") && contains("grou")) and will catch
unrelated IDs; tighten it by requiring the id to start with "rb" (or match a
stricter regex) before mapping to "rb_group" — e.g., replace the condition with
normalizedId.startsWith("rb") && normalizedId.contains("grou") or use a regex
like normalizedId.matches("^rb[_-]?grou.*") so only intended IDs are normalized
to "rb_group" while leaving other values untouched; keep the first branch for
"radio_grou" -> "radio_group" and return via context.resolveId as before.

---

Nitpick comments:
In
`@cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/xml/AndroidWidget.kt`:
- Around line 11-12: The sealed class AndroidWidget exposes mutable properties
idOverride and extraAttrs which makes instances stateful; either make these
properties immutable (convert to constructor params or vals) or, if you must
keep them mutable, add a clear documentation comment on AndroidWidget explaining
the invariant that WidgetFactory must create fresh widget instances for each
render and widgets must never be cached/reused; alternatively provide an
explicit copy/withIdOverride/withExtraAttrs factory method on AndroidWidget so
callers create modified copies instead of mutating idOverride/extraAttrs
directly.

In
`@cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/xml/WidgetFactory.kt`:
- Around line 119-125: The getMarginEndForHorizontalGap function always emits an
android:layout_marginEnd attribute even when gap is 0; change the logic so after
computing gap = maxOf(0, nextBox.x - (currentBox.x + currentBox.w)) you return
emptyMap() when gap == 0 and only return mapOf("android:layout_marginEnd" to
"${gap}dp") when gap > 0, keeping the early bounds check on currentIndex and
using the existing variables (boxes, currentIndex, currentBox, nextBox, gap) to
locate where to add this conditional.

In
`@cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/utils/TextCleaner.kt`:
- Line 27: The inline Regex pattern
".replace(Regex(\"^[\\\\[\\\\]()●○□☑✓-]+\\\\s*\"), \"\")" in
cleanTextPreservingLeadingO duplicates the existing leadingMarkerRegex; replace
the inline Regex call with a reuse of the leadingMarkerRegex constant (e.g.,
.replace(leadingMarkerRegex, "")) so future changes to leadingMarkerRegex apply
consistently across the function.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 47cb9a56-13af-41fd-a864-dfa247fffce3

📥 Commits

Reviewing files that changed from the base of the PR and between f63d69c and 808a214.

📒 Files selected for processing (9)
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/FuzzyAttributeParser.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/grammar/AttributeValidator.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/grammar/UiGrammarValidator.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/grammar/WidgetGrammar.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/xml/AndroidConstants.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/xml/AndroidWidget.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/xml/LayoutRenderer.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/xml/WidgetFactory.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/utils/TextCleaner.kt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants