Skip to content

Ship real bindings for Xamarin.AndroidX.Compose.UI / Foundation / Foundation.Layout#1420

Open
jonathanpeppers wants to merge 6 commits into
mainfrom
jonathanpeppers/compose-ui-foundation-real-bindings
Open

Ship real bindings for Xamarin.AndroidX.Compose.UI / Foundation / Foundation.Layout#1420
jonathanpeppers wants to merge 6 commits into
mainfrom
jonathanpeppers/compose-ui-foundation-real-bindings

Conversation

@jonathanpeppers
Copy link
Copy Markdown
Member

What

Follow-up to #1418. Ship real C# bindings for the three peer Compose recipes that previously shipped as empty managed facades:

  • Xamarin.AndroidX.Compose.UI / Xamarin.AndroidX.Compose.UI.Android (~1.26 MB)
  • Xamarin.AndroidX.Compose.Foundation / Xamarin.AndroidX.Compose.Foundation.Android (~975 KB)
  • Xamarin.AndroidX.Compose.Foundation.Layout / Xamarin.AndroidX.Compose.Foundation.Layout.Android (~267 KB)

Each *-android recipe previously contained a package-wide <remove-node path="/api/package" /> strip that erased every type. This PR replaces those strips with targeted <remove-node> entries that only drop the specific Kotlin idioms (inline classes, KMP collection erasure, typealiases, companion-object Key collisions, abstract Invokers, etc.) the JNI binding generator cannot project into C#.

Reference

Mirrors the proven recipe in jonathanpeppers/compose-net@d1c6a5e — same approach we used for Xamarin.AndroidX.Compose.Runtime in #1418:

Changes

File Purpose
source/androidx.compose.ui/ui-android/Transforms/Metadata.xml Drop the package-wide strip; add 27 managedName overrides (UiUI) plus 11 targeted <remove-node> entries.
source/androidx.compose.foundation/foundation-android/Transforms/Metadata.xml Drop the package-wide strip; add targeted <remove-node> entries for inline-class overloads, LazyLayoutPinnedItemList, KeyMapping, and (new in 1.11.x) StyleState / SelectionGestures_androidKt.
source/androidx.compose.foundation/foundation-layout-android/Transforms/Metadata.xml Drop the package-wide strip; drop PaddingValues.Absolute.
source/AndroidXProject.cshtml Extend the "C# bindings are not provided" description exception list to cover the 6 new packages.
config.json Bump nugetVersion 1.11.11.11.1.1 for the 6 affected packages (NuGet-only revision bump).
source/androidx.compose.{ui,foundation}/*/PublicAPI/PublicAPI.Unshipped.txt Regenerated baselines (auto-produced by _GeneratePublicApiFiles in Directory.Build.targets).

UI casing

The UI Metadata.xml applies managedName="…UI…" on every androidx.compose.ui* package (27 entries). The default would produce AndroidX.Compose.Ui.*; once these NuGets ship real types, this is a permanent ABI decision, so we go with the conventional uppercase .UI to match every other *UI* namespace/assembly in this repo.

Targeted <remove-node> entries

ui-android (11):

  • AndroidUiFrameClock, MotionDurationScale.Key + interface MotionDurationScale/field Key, InfiniteAnimationPolicy.Key + interface InfiniteAnimationPolicy/field Key — same pattern as MonotonicFrameClock.Key in runtime: nested companion Key collides with inherited CoroutineContext.Element.key (CS0119 / CS0572).
  • Modifier.Companion — Kotlin companion vs nested generator-emitted Companion collision.
  • Modifier.Node — CS0542 (member-same-as-enclosing).
  • FrameRateKt.preferredFrameRate, ScaleFactorKt.times-* — Kotlin inline-class hash-mangled overloads collapse to identical C# signatures.
  • ModifierLocalReadScope, ModifierLocalModifierNode — Kotlin typealias that doesn't project as an interface.
  • SubcomposeSlotReusePolicy, SubcomposeSlotReusePolicy.SlotIdsSet — Kotlin MutableSet projected into java.util.Collection breaks the Invoker.
  • RequestDisallowInterceptTouchEventFunction1-as-Java-class projection where the interface method isn't surfaced.
  • ModifierNodeElement — abstract any/all(Function1) can't be satisfied by the auto-generated Invoker.

foundation-android (compose-net 3 + 2 new for 1.11.x):

  • CornerSizeKt.CornerSize-*, LazyLayoutCacheWindowKt.LazyLayoutCacheWindow-* — inline-class hash-mangled overload collision.
  • LazyLayoutPinnedItemList — Kotlin MutableList re-declares java.util.List operations (CS0535/CS0738 on Add/AddAll/Get/Set/Size/SubList/etc.).
  • KeyMapping, KeyMappingKt, KeyMapping_androidKt — returns java.lang.Enum<KeyCommand> which doesn't project.
  • New in 1.11.x: StyleState, MutableStyleState, StyleStateKey, StyleStateKt — abstract bool getters (IsChecked/IsPressed/IsSelected/IsHovered/IsEnabled/IsFocused) not implemented by the auto-generated MutableStyleState subclass (CS0534).
  • New in 1.11.x: SelectionGestures_androidKt — references AndroidX.Compose.Foundation.Text.Selection.ISelectionAdjustment which the generator does not emit as an interface (only its *Kt companion ships).

foundation-layout-android (1):

  • PaddingValues.Absolute — Kotlin object whose JVM signatures don't include the calculate*Padding() impls, so the auto-generated C# class is broken.

Verification

Locally on Windows, all three *-android csproj projects build clean for both net9.0-android35.0 and net10.0-android36.0:

Xamarin.AndroidX.Compose.UI.Android.dll                  ~1,262 KB (net10)  ~1,218 KB (net9)
Xamarin.AndroidX.Compose.Foundation.Android.dll          ~975 KB   (net10)  ~941 KB   (net9)
Xamarin.AndroidX.Compose.Foundation.Layout.Android.dll   ~267 KB   (net10)  ~256 KB   (net9)

The PublicAPI baseline diffs are large and intentional — they are the surface area that was previously hidden behind the package-wide <remove-node> strips.

Closes #1416

jonathanpeppers and others added 2 commits May 21, 2026 15:18
`Xamarin.AndroidX.Compose.Runtime.Android` (artifactId
`androidx.compose.runtime:runtime-android`) previously shipped as an
empty managed facade because `Transforms/Metadata.xml` contained a
package-wide `<remove-node path="/api/package" />` strip. This change
replaces that strip with targeted `<remove-node>` entries for the
Kotlin idioms the JNI binding generator cannot project into C#, so the
package now ships ~980 lines of real public API.

Mirrors the proven recipe in jonathanpeppers/compose-net@d1c6a5e:
- src/ComposeNet.Bindings.Runtime/Transforms/Metadata.xml
- src/ComposeNet.Bindings.Runtime/ComposeNet.Bindings.Runtime.csproj

Targeted strips (14 from compose-net + 4 added during this work):

  Compose-net entries:
  - SnapshotState{List,Map,Set} + *Kt (KMP collection-jvm interface
    erasure mismatches `java.util.*`)
  - MutableSnapshot + Snapshot.getReadObserver (Kotlin typealias)
  - AbstractApplier (generic IApplier erasure)
  - BroadcastFrameClock, PausableMonotonicFrameClock (missing
    CoroutineContext.Element.key override)
  - Mutable{Int,Long,Float,Double}State + non-mutable peers (primitive
    `value` hides MutableState<T>.value setter)
  - MonotonicFrameClock.Key, SnapshotContextElement.Key (companion
    object collides with inherited `key` property)
  - ComposerKt.isAfterFirstChild (overload collapse)

  Added here:
  - androidx.compose.runtime.composer.{gapbuffer,linkbuffer}{,.changelist}
    (internal Kotlin packages whose namespace collides with the
    `Composer` class — C# cannot have a namespace and type with the
    same FQN)

Other changes:
- `source/_PackageLevelCustomizations.cshtml`: inject
  `<AndroidIgnoredJavaDependency Include="androidx.collection:collection-jvm:1.5.0" />`
  into the generated `Xamarin.AndroidX.Compose.Runtime.Android` csproj.
- `source/AndroidXProject.cshtml`: drop the "C# bindings are not
  provided" note from the Compose.Runtime / Compose.Runtime.Android
  package description (no longer accurate).
- `source/androidx.compose.runtime/runtime-android/PublicAPI/PublicAPI.Unshipped.txt`:
  regenerated baseline (auto-produced by `_GeneratePublicApiFiles` in
  `Directory.Build.targets`).

`Xamarin.AndroidX.Compose.Runtime` (artifactId `runtime`) is the KMP
umbrella with no bytecode of its own; it transitively gains the real
managed surface via its `ProjectReference` to
`runtime-android`, so the consumer-facing nuget also ships real
bindings now.

Verified locally: both `androidx.compose.runtime.runtime-android` and
`androidx.compose.runtime.runtime` build clean for `net9.0-android`
and `net10.0-android`.

Closes #1415

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Now that Xamarin.AndroidX.Compose.Runtime ships real bindings (#1415),
androidx.compose.runtime.Composer is resolvable from downstream consumers
like Material3. The binding generator now emits MaterialTheme.getMotionScheme,
which previously was silently skipped because its `Composer` parameter type
was unresolvable.

The emitted method references AndroidX.Compose.Material3.IMotionScheme, but
MotionScheme is a Kotlin-internal interface (`visibility=""` in api.xml) that
the generator never produces, causing CS0234. Strip the method via metadata;
it was never on main either, so public API surface is unchanged.

Also regenerates PublicAPI baselines for other Compose.Runtime consumers
whose getter methods are now resolvable: lifecycle-runtime-compose-android,
runtime-retain-android, navigationevent-compose-android.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers jonathanpeppers force-pushed the jonathanpeppers/compose-ui-foundation-real-bindings branch from 27cf0ab to 0cbd637 Compare May 22, 2026 13:20
Three Compose.Runtime consumer packages gained many new public methods
in 5efe3f7 once IComposer became resolvable. Bump nugetVersion so a
restore at the old version vs the new produces different binaries:

- Xamarin.AndroidX.Compose.Material3Android 1.4.0.2 -> 1.4.0.3
- Xamarin.AndroidX.Lifecycle.Runtime.Compose.Android 2.10.0.2 -> 2.10.0.3
- Xamarin.AndroidX.NavigationEvent.Compose.Android 1.1.1 -> 1.1.1.1

Xamarin.AndroidX.Compose.Runtime.Retain.Android was already bumped to
1.11.1.1 in the earlier bulk Compose.Runtime.* bump; that single bump
already covers its surface change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers jonathanpeppers force-pushed the jonathanpeppers/compose-ui-foundation-real-bindings branch from 0cbd637 to 96f2e67 Compare May 22, 2026 13:49
Now that Xamarin.AndroidX.Compose.Runtime ships real bindings (#1415),
13 new AndroidX.Compose.Runtime.* namespaces are emitted. Append them
to the published list to satisfy the verify-namespace-file CI check.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers jonathanpeppers force-pushed the jonathanpeppers/compose-ui-foundation-real-bindings branch from 96f2e67 to 5a4314e Compare May 22, 2026 15:55
Base automatically changed from jonathanpeppers/compose-runtime-real-bindings to main May 22, 2026 19:52
@jonathanpeppers jonathanpeppers marked this pull request as ready for review May 22, 2026 19:53
Copilot AI review requested due to automatic review settings May 22, 2026 19:53
…ndation.Layout (#1416)

Follow-up to #1418. The three peer recipes `Xamarin.AndroidX.Compose.UI`,
`Xamarin.AndroidX.Compose.Foundation`, and
`Xamarin.AndroidX.Compose.Foundation.Layout` all carried the same
package-wide `<remove-node path="/api/package" />` strip and shipped as
empty managed facades. This replaces the strips with targeted
`<remove-node>` entries (mirroring jonathanpeppers/compose-net@d1c6a5e)
so each `*-android` recipe now ships real public bindings.

ui-android (~1.26 MB net10.0-android36.0):
- managedName overrides so all 27 androidx.compose.ui* packages project
  as AndroidX.Compose.UI.* (uppercase) instead of the default
  AndroidX.Compose.Ui.*. This is a permanent ABI decision now that these
  NuGets ship real types, matching the casing the repo already uses for
  the rest of the project (.UI suffixed assemblies).
- AndroidUiFrameClock / MotionDurationScale.Key /
  InfiniteAnimationPolicy.Key: same problem as MonotonicFrameClock.Key
  in runtime (CoroutineContext.Element.key collision).
- Modifier.Companion / Modifier.Node: CS0542 (member-same-as-enclosing)
  and namespace/type FQN collision.
- FrameRateKt.preferredFrameRate / ScaleFactorKt.times-: Kotlin
  inline-class hash-mangled overloads that collapse to the same C#
  signature.
- ModifierLocalReadScope / ModifierLocalModifierNode: typealias
  projection.
- SubcomposeSlotReusePolicy / SubcomposeSlotReusePolicy.SlotIdsSet:
  MutableSet erasure mismatches java.util.Collection on the Invoker.
- RequestDisallowInterceptTouchEvent: Function1-as-Java-class projection
  doesn't surface the interface method.
- ModifierNodeElement: abstract any/all(Function1) can't be satisfied by
  the auto-generated Invoker.

foundation-android (~975 KB net10.0-android36.0):
- CornerSizeKt.CornerSize- / LazyLayoutCacheWindowKt.LazyLayoutCacheWindow-:
  inline-class hash-mangled overload collision.
- LazyLayoutPinnedItemList: MutableList re-declares java.util.List
  operations (CS0535/CS0738 on Add/AddAll/Get/Set/Size/SubList/etc.).
- KeyMapping / KeyMappingKt / KeyMapping_androidKt: returns
  java.lang.Enum<KeyCommand> which doesn't project.
- StyleState / MutableStyleState / StyleStateKey / StyleStateKt (new in
  1.11.x): abstract bool getters not implemented by the auto-generated
  subclass (CS0534).
- SelectionGestures_androidKt (new in 1.11.x): references
  AndroidX.Compose.Foundation.Text.Selection.ISelectionAdjustment which
  the generator does not emit as an interface.

foundation-layout-android (~267 KB net10.0-android36.0):
- PaddingValues.Absolute: Kotlin object whose JVM signatures don't
  include the calculate*Padding() impls — auto-generated C# class is
  broken.

Other changes:
- source/AndroidXProject.cshtml: extend the "C# bindings are not
  provided" description exception to cover the six new packages
  (Compose.UI, Compose.UI.Android, Compose.Foundation,
  Compose.Foundation.Android, Compose.Foundation.Layout,
  Compose.Foundation.Layout.Android).
- config.json: bump nugetVersion 1.11.1 -> 1.11.1.1 for the six
  packages whose surface area is changing.
- source/androidx.compose.{ui,foundation}/*/PublicAPI/PublicAPI.Unshipped.txt:
  regenerated baselines (auto-produced by _GeneratePublicApiFiles in
  Directory.Build.targets).

Verified locally on Windows: all three *-android csproj projects build
clean for net9.0-android and net10.0-android.

Closes #1416

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers jonathanpeppers force-pushed the jonathanpeppers/compose-ui-foundation-real-bindings branch from 6ac3be6 to 13f3fc3 Compare May 22, 2026 19:56
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR converts the Xamarin.AndroidX.Compose.UI*, Xamarin.AndroidX.Compose.Foundation*, and Xamarin.AndroidX.Compose.Foundation.Layout* packages from “empty managed facades” into real, shipped bindings by removing the previous package-wide metadata stripping and replacing it with targeted removals for Kotlin/JNI binding edge-cases. It also updates packaging/versioning metadata and regenerates PublicAPI baselines to reflect the newly exposed surface area.

Changes:

  • Replace package-wide <remove-node path="/api/package" /> transforms with targeted <remove-node> exclusions for known generator-incompatible Kotlin patterns.
  • Apply Compose UI namespace casing decisions (UiUI) via managedName overrides.
  • Bump NuGet-only revisions and regenerate API/namespace baselines for the newly shipped bindings.

Reviewed changes

Copilot reviewed 16 out of 18 changed files in this pull request and generated no comments.

Show a summary per file
File Description
source/AndroidXProject.cshtml Extends the “no C# bindings provided” description exception list to include the newly-real Compose UI/Foundation packages.
source/androidx.compose.ui/ui-android/Transforms/Metadata.xml Removes package-wide stripping; adds managedName overrides for AndroidX.Compose.UI.* and targeted removals for binding-breakers.
source/androidx.compose.foundation/foundation-android/Transforms/Metadata.xml Removes package-wide stripping; adds targeted removals for inline-class overload collisions and known problematic types.
source/androidx.compose.foundation/foundation-layout-android/Transforms/Metadata.xml Removes package-wide stripping; drops PaddingValues.Absolute which produces broken managed output.
source/androidx.compose.foundation/foundation-android/PublicAPI/PublicAPI.Unshipped.txt Regenerated PublicAPI baseline reflecting newly exposed Foundation API surface.
source/androidx.compose.foundation/foundation-layout-android/PublicAPI/PublicAPI.Unshipped.txt Regenerated PublicAPI baseline reflecting newly exposed Foundation.Layout API surface.
published-namespaces.txt Adds newly published AndroidX.Compose.* namespaces now that bindings are emitted.
config.json NuGet-only revision bump (1.11.11.11.1.1) for the affected Compose packages.
Comments suppressed due to low confidence (1)

source/AndroidXProject.cshtml:41

  • The package description exception logic is a long chain of Model.NuGetPackageId != ... checks, which is hard to read and easy to miss future Compose packages that gain bindings. Consider refactoring this into a small set/array of package IDs (or a helper like IsComposeBindingsProvided) and using Contains/Any to make updates simpler.
       - XAOBS001: While this member is 'public', Google considers it internal API and reserves the right to modify or delete it in the future. Use at your own risk. 
       - NU1605: Detected package downgrade
    -->

…se-ui-foundation-real-bindings

# Conflicts:
#	published-namespaces.txt
#	source/AndroidXProject.cshtml
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.

[Xamarin.AndroidX.Compose.UI / Foundation / Foundation.Layout] Stop stripping the whole package; ship real bindings

2 participants