Skip to content

Remove LayoutViewFactory + refactor FeatureView to make it compose-first#1

Open
iantybo wants to merge 8 commits into
mainfrom
bezverhni/feature_view_refactor
Open

Remove LayoutViewFactory + refactor FeatureView to make it compose-first#1
iantybo wants to merge 8 commits into
mainfrom
bezverhni/feature_view_refactor

Conversation

@iantybo

@iantybo iantybo commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Mirror of instacart/formula#511 for CodeRabbit review.

Runs the changes from instacart/formula PR #511 against the same base commit (f8f1aa3) the original ran against.

Original PR: instacart/formula#511

Risk Score: High - Possible Team Escalation Required

Summary

This PR performs a large breaking refactor of the Formula Android framework to a Jetpack Compose-first rendering model. It removes the separate formula-android-compose module and consolidates compose rendering into formula-android, while eliminating the legacy XML/layout-based rendering abstractions (LayoutViewFactory, FeatureView, ViewInstance, and ViewFactory.Params/fromLayout).

Core Changes

  • Rendering architecture switched to declarative Compose
    • RouteEnvironment.RouteDelegate now renders via an @Composable Content(...) hook (instead of an imperative createView(...)).
    • FormulaFragment now uses a ComposeView + MutableState (outputState) and wires routeDelegate.setOutput(...) to update state.
  • View factory API reshaped
    • ViewFactory changed to a sealed interface (marker/dispatch surface) and removes create(...)/Params and layout inflation helpers.
    • New formula-android.ComposeViewFactory base class introduced in the main module with @Composable Content(model) and optional initialModel().
    • Legacy composition entry points are removed from the old module; a deprecated typealias exists to help point users toward the new formula-android symbol.
  • Deletion of legacy infrastructure
    • Removed LayoutViewFactory, FeatureView, ViewInstance, plus related internal classes and tests around those components.

Ecosystem / Usage Updates

  • Modules and samples migrated to ComposeViewFactory
    • Samples updated to depend on :formula-android (not :formula-android-compose) and implement @Composable Content(...) instead of XML/layout factory patterns.
    • Test utilities updated similarly (e.g., TestViewFactory now extends ComposeViewFactory and drives rendering through Compose).
  • CI/build/release workflow cleanup
    • GitHub Actions and archive/upload scripts remove builds/publishing steps for :formula-android-compose.

Notable Follow-up Risk

  • Docs still reference the removed artifact: docs/index.md contains an implementation 'com.instacart.formula:formula-android-compose:0.7.1' line, which may now be outdated or incorrect relative to the module removal.

Impact Assessment

  • Breaking change magnitude: very high
    • Core public/semipublic rendering APIs were removed or substantially redesigned.
    • Downstream consumers must migrate to ComposeViewFactory + RouteDelegate.Content(...).
  • Test/validation risk
    • Several tests and assertions were removed or adjusted to accommodate the new Compose/state behavior, increasing the likelihood that runtime/build compatibility issues may surface in downstream integrations.

@iantybo

iantybo commented Jun 15, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The formula-android-compose Gradle module is dissolved. ComposeViewFactory moves into formula-android as an abstract class implementing a new sealed interface ViewFactory. FormulaFragment replaces its FeatureView/XML rendering path with a ComposeView backed by MutableState. FeatureView, ViewInstance, LayoutViewFactory, and related XML infrastructure are deleted. The todoapp sample is rewritten using Compose. Test utilities, CI workflows, and build scripts are updated accordingly.

Changes

Compose-first rendering migration

Layer / File(s) Summary
ViewFactory sealed interface and ComposeViewFactory contract
formula-android/src/main/java/com/instacart/formula/android/ViewFactory.kt, formula-android/src/main/java/com/instacart/formula/android/ComposeViewFactory.kt, formula-android/src/main/java/com/instacart/formula/android/compose/ComposeViewFactory.kt, formula-android/src/main/java/com/instacart/formula/android/FeatureFactory.kt
ViewFactory is changed from a fun interface with create/Params/fromLayout to a sealed interface with a RenderModel : Any constraint. ComposeViewFactory is added to formula-android as the primary sealed subtype with an abstract @Composable Content and optional initialModel hook. A deprecated typealias in com.instacart.formula.android.compose preserves backward compatibility. FeatureFactory KDoc is updated to show the new pattern.
FormulaFragment and RouteDelegate Compose rendering pipeline
formula-android/src/main/java/com/instacart/formula/android/RouteEnvironment.kt, formula-android/src/main/java/com/instacart/formula/android/FormulaFragment.kt
RouteDelegate replaces the createView open method with a @Composable Content method that dispatches over the ViewFactory sealed hierarchy. FormulaFragment replaces the FeatureView field with a MutableState<Any?>, returns a ComposeView from onCreateView, and renders via routeDelegate.Content. setState updates outputState via a setOutput callback. tryToSetState and the stored featureView/output fields are removed.
formula-android Compose build configuration and module setup
formula-android/build.gradle.kts, settings.gradle.kts, formula-android-compose/.gitignore
formula-android/build.gradle.kts removes an unused DokkaTask import, enables Compose via buildFeatures.compose, and adds the compose.ui api dependency. settings.gradle.kts removes the :formula-android-compose module include. formula-android-compose/.gitignore is cleared, completing module dissolution.
TestViewFactory rewrite and test suite updates
test-utils/android/build.gradle.kts, test-utils/android/src/main/java/com/instacart/testutils/android/TestViewFactory.kt, formula-android-tests/src/test/java/com/instacart/formula/TestFeatureFactory.kt, formula-android-tests/src/test/java/com/instacart/formula/FormulaFragmentTest.kt
test-utils/android enables Compose in its build config. TestViewFactory is rewritten to extend ComposeViewFactory with a single-arg onRender callback and a @Composable Content override. TestFeatureFactory updates the lambda arity from two parameters to one. FormulaFragmentTest removes a crash path test case and adds main-looper idling in sendStateUpdate when called from the main thread to drain pending Compose recomposition before assertions.
todoapp sample migrated to Compose
samples/todoapp/build.gradle.kts, samples/todoapp/src/main/java/com/examples/todoapp/tasks/TaskListFeatureFactory.kt, samples/todoapp/src/main/java/com/examples/todoapp/tasks/TaskListScreen.kt, samples/todoapp/src/main/java/com/examples/todoapp/data/TaskRepoImpl.kt, samples/todoapp/src/main/res/layout/*, samples/todoapp/src/main/res/menu/tasks_list_menu.xml
TaskListFeatureFactory switches from LayoutViewFactory to ComposeViewFactory with a Content composable calling TaskListScreen. TaskListScreen is added as a new Compose UI with scaffold, lazy list, filter dropdown app bar, and task rows. TaskListRenderView and all XML layouts are removed. TaskRepoImpl flow builder is refactored to use flow { ... emit(Unit) } with no behavior change.
Other samples import updates, CI workflows, and docs cleanup
samples/navigation-fragments/build.gradle.kts, samples/navigation-fragments/src/main/java/com/instacart/formula/navigation/CounterFragmentViewFactory.kt, samples/stopwatch-compose/build.gradle.kts, samples/stopwatch-compose/src/main/java/com/instacart/formula/compose/stopwatch/StopwatchFeatureFactory.kt, .buildscript/upload_archives.sh, .github/workflows/build-workflow.yml, .github/workflows/publish.yml, AGENTS.md, docs/Formula-Android.md
navigation-fragments and stopwatch-compose remove :formula-android-compose dependencies and update ComposeViewFactory imports to the new com.instacart.formula.android package. CI workflows remove :formula-android-compose build steps. AGENTS.md and docs/Formula-Android.md are updated to reflect the merged module and Compose-based API examples.

Sequence Diagram

sequenceDiagram
    participant FormulaFragment
    participant ComposeView
    participant RouteDelegate
    participant ComposeViewFactory

    FormulaFragment->>ComposeView: onCreateView returns ComposeView
    ComposeView->>RouteDelegate: setContent calls Content(routeId, viewFactory, model)
    RouteDelegate->>ComposeViewFactory: dispatch → Content(model)
    Note over ComposeViewFactory: `@Composable` Content renders UI
    FormulaFragment->>RouteDelegate: setState(newState) → setOutput(model) callback
    RouteDelegate-->>FormulaFragment: outputState.value updated (MutableState recompose)
Loading

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Performance And Algorithmic Complexity Review ❌ Error RouteEnvironment.Content (lines 33-38) has incomplete when expression over sealed ViewFactory without else clause in hot composable path, creating silent rendering failure risk if a ViewFactory sub... Add else clause to when statement in RouteEnvironment.Content to handle unexpected ViewFactory types or throw error if new sealed subtypes exist; consider logging warning if only ComposeViewFactory should ever exist.
Docstring Coverage ⚠️ Warning Docstring coverage is 13.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary architectural change across this changeset: removing LayoutViewFactory and refactoring the rendering model to be Compose-first.
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.
Gdpr And Pii Data Protection Compliance ✅ Passed PR is a UI refactoring (View→Compose) with no PII fields, plaintext logging, network exposure, or insecure storage. No GDPR violations detected.
✨ 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 bezverhni/feature_view_refactor

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
formula-android/src/main/java/com/instacart/formula/android/compose/ComposeViewFactory.kt (1)

16-16: 💤 Low value

Type parameter constraint mismatch with target class.

The typealias uses <R> without the : Any constraint, while the target ComposeViewFactory requires <RenderModel : Any>. This compiles because the constraint is enforced at the target, but the inconsistency may confuse consumers reviewing the deprecated API.

Not in the production outage path - this is a deprecation bridge that will be removed.

Suggested fix
-typealias ComposeViewFactory<R> = com.instacart.formula.android.ComposeViewFactory<R>
+typealias ComposeViewFactory<R : Any> = com.instacart.formula.android.ComposeViewFactory<R>
🤖 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
`@formula-android/src/main/java/com/instacart/formula/android/compose/ComposeViewFactory.kt`
at line 16, The typealias ComposeViewFactory is missing the type parameter
constraint that exists on the target class. Add the `: Any` constraint to the
type parameter `R` in the typealias definition to match the target
ComposeViewFactory class's type parameter constraint `RenderModel : Any`. This
ensures consistency for API consumers and clarifies the expected behavior of the
deprecated typealias.
🤖 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.

Nitpick comments:
In
`@formula-android/src/main/java/com/instacart/formula/android/compose/ComposeViewFactory.kt`:
- Line 16: The typealias ComposeViewFactory is missing the type parameter
constraint that exists on the target class. Add the `: Any` constraint to the
type parameter `R` in the typealias definition to match the target
ComposeViewFactory class's type parameter constraint `RenderModel : Any`. This
ensures consistency for API consumers and clarifies the expected behavior of the
deprecated typealias.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: c18720ee-c39b-468a-b5d3-9c5c906c071a

📥 Commits

Reviewing files that changed from the base of the PR and between f8f1aa3 and 836c0f3.

📒 Files selected for processing (42)
  • .buildscript/upload_archives.sh
  • .github/workflows/build-workflow.yml
  • .github/workflows/publish.yml
  • AGENTS.md
  • docs/Formula-Android.md
  • formula-android-compose/.gitignore
  • formula-android-compose/build.gradle.kts
  • formula-android-compose/gradle.properties
  • formula-android-compose/src/main/AndroidManifest.xml
  • formula-android-compose/src/main/java/com/instacart/formula/android/compose/ComposeViewFactory.kt
  • formula-android-tests/src/test/java/com/instacart/formula/FormulaFragmentTest.kt
  • formula-android-tests/src/test/java/com/instacart/formula/TestFeatureFactory.kt
  • formula-android/build.gradle.kts
  • formula-android/src/main/java/com/instacart/formula/android/ComposeViewFactory.kt
  • formula-android/src/main/java/com/instacart/formula/android/FeatureFactory.kt
  • formula-android/src/main/java/com/instacart/formula/android/FeatureView.kt
  • formula-android/src/main/java/com/instacart/formula/android/FormulaFragment.kt
  • formula-android/src/main/java/com/instacart/formula/android/LayoutViewFactory.kt
  • formula-android/src/main/java/com/instacart/formula/android/RouteEnvironment.kt
  • formula-android/src/main/java/com/instacart/formula/android/ViewFactory.kt
  • formula-android/src/main/java/com/instacart/formula/android/ViewInstance.kt
  • formula-android/src/main/java/com/instacart/formula/android/compose/ComposeViewFactory.kt
  • formula-android/src/main/java/com/instacart/formula/android/views/DelegateLayoutViewFactory.kt
  • formula-android/src/main/java/com/instacart/formula/android/views/InflatedViewInstance.kt
  • formula-android/src/test/java/com/instacart/formula/android/ViewFactoryTest.kt
  • formula-android/src/test/java/com/instacart/formula/android/views/ViewInstanceTest.kt
  • samples/navigation-fragments/build.gradle.kts
  • samples/navigation-fragments/src/main/java/com/instacart/formula/navigation/CounterFragmentViewFactory.kt
  • samples/stopwatch-compose/build.gradle.kts
  • samples/stopwatch-compose/src/main/java/com/instacart/formula/compose/stopwatch/StopwatchFeatureFactory.kt
  • samples/todoapp/build.gradle.kts
  • samples/todoapp/src/main/java/com/examples/todoapp/data/TaskRepoImpl.kt
  • samples/todoapp/src/main/java/com/examples/todoapp/tasks/TaskListFeatureFactory.kt
  • samples/todoapp/src/main/java/com/examples/todoapp/tasks/TaskListRenderView.kt
  • samples/todoapp/src/main/java/com/examples/todoapp/tasks/TaskListScreen.kt
  • samples/todoapp/src/main/res/layout/task_item.xml
  • samples/todoapp/src/main/res/layout/task_list.xml
  • samples/todoapp/src/main/res/menu/tasks_list_menu.xml
  • settings.gradle.kts
  • test-utils/android/build.gradle.kts
  • test-utils/android/src/main/java/com/instacart/testutils/android/TestViewFactory.kt
  • test-utils/android/src/main/res/layout/test_fragment_layout.xml
💤 Files with no reviewable changes (22)
  • samples/todoapp/src/main/res/layout/task_item.xml
  • formula-android-compose/.gitignore
  • .github/workflows/publish.yml
  • samples/todoapp/src/main/res/layout/task_list.xml
  • test-utils/android/src/main/res/layout/test_fragment_layout.xml
  • formula-android/src/main/java/com/instacart/formula/android/views/InflatedViewInstance.kt
  • formula-android-compose/src/main/AndroidManifest.xml
  • formula-android/src/test/java/com/instacart/formula/android/ViewFactoryTest.kt
  • formula-android/src/main/java/com/instacart/formula/android/FeatureView.kt
  • formula-android-compose/gradle.properties
  • formula-android/src/test/java/com/instacart/formula/android/views/ViewInstanceTest.kt
  • formula-android/src/main/java/com/instacart/formula/android/LayoutViewFactory.kt
  • .buildscript/upload_archives.sh
  • formula-android-compose/build.gradle.kts
  • formula-android/src/main/java/com/instacart/formula/android/ViewInstance.kt
  • formula-android/src/main/java/com/instacart/formula/android/views/DelegateLayoutViewFactory.kt
  • samples/todoapp/src/main/java/com/examples/todoapp/tasks/TaskListRenderView.kt
  • samples/todoapp/src/main/res/menu/tasks_list_menu.xml
  • samples/stopwatch-compose/build.gradle.kts
  • samples/navigation-fragments/build.gradle.kts
  • settings.gradle.kts
  • formula-android-compose/src/main/java/com/instacart/formula/android/compose/ComposeViewFactory.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