Compose UI.Graphics: SolidColor(long) constructor stripped due to Kotlin @JvmInline value class param (sibling of java-interop#1440 for ctors)
@jonathanpeppers is already working on this.
Since Jun 11, 2026.
Assessment
This issue has not been assessed yet.
Description
Background
Kotlin @JvmInline value class parameters strip the constructor of a
binding even when the JVM bytecode signature is plain primitive shape and
the surrounding type is otherwise fully bound. This is a constructor-side
sibling of dotnet/java-interop#1440 (which targets static methods like
Button-bWB7cM8 / Typography(...)) and the closest analogue is
#1453 — but
that issue focuses on a stripped static factory function. Stripped
constructors are not currently tracked.
Concrete repro: androidx.compose.ui.graphics.SolidColor
Kotlin source (1.11.x):
@Immutable
class SolidColor(val value: Color) : ShaderBrush() { … }
Color is @JvmInline value class Color(val value: ULong), so the JVM
bytecode for the ctor is plain primitive — <init>(J)V. There is no
mangling on the constructor itself (Kotlin's hash-mangling only applies
to methods), but the binder still drops it.
Xamarin.AndroidX.Compose.UI.Graphics.Android 1.11.2.1 decompiled with
ilspycmd:
[Register("androidx/compose/ui/graphics/SolidColor", DoNotGenerateAcw = true)]
public sealed class SolidColor : Brush, IInterpolatable, …
{
// value-class-param methods with mangled names ARE kept:
[Register("getValue-0d7_KjU", "()J", "")] public long Value { get; }
[Register("applyTo-Pq9zytI", "(JLandroidx/compose/ui/graphics/Paint;F)V", "")]
public sealed override void ApplyTo(long size, IPaint p, float alpha) { … }
[Register("lerp", "(Ljava/lang/Object;F)Ljava/lang/Object;", "")]
public Object? Lerp(Object? other, float t) { … }
// Only the internal acw ctor survives. The public (J)V ctor is gone:
internal SolidColor(nint javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer) { }
// <-- where is `public SolidColor(long packedColor)` ?
}
source/androidx.compose.ui/ui-graphics-android/PublicAPI/PublicAPI.Unshipped.txt
confirms the gap — the only SolidColor ctor in the public API is the
internal (nint, JniHandleOwnership) one. There is no SolidColor(long).
This is not unique to SolidColor: every Compose class whose primary
ctor takes a value-class parameter has the same problem (Path,
Outline.Rectangle, several BrushPainter shapes, …). The
androidx.compose.ui.graphics.Color type itself loses both its (J)V
ctor and its box-impl(J)Color static factory for the same reason.
Real-world impact
jonathanpeppers/Microsoft.AndroidX.Compose#251 adds
Brush.SolidColor(Color) and the gradient factories. Because the bound
SolidColor has no callable ctor, the PR ships a hand-written JNI
fallback in BrushBridges.cs:
// `new androidx.compose.ui.graphics.SolidColor(Color)` — the ctor
// is stripped because its parameter is a value-class `Color`.
internal static unsafe IntPtr BrushSolidColor(long color)
{
if (s_solidColor_ctor == IntPtr.Zero)
{
s_solidColor_class = Java.Lang.Class.FromType(
typeof(AndroidX.Compose.UI.Graphics.SolidColor)).Handle;
s_solidColor_ctor = JNIEnv.GetMethodID(
s_solidColor_class, "<init>", "(J)V");
}
var args = stackalloc JValue[1];
args[0] = new JValue(color);
return JNIEnv.NewObject(s_solidColor_class, s_solidColor_ctor, args);
}
The same PR also reaches for Color.box-impl(J) (a static factory) and
Brush.Companion (a static field) directly via JNI — those have other
trackers — but the ctor case is the new one and is what this issue is
about.
Question: would <add-node> + <constructor> be an acceptable metadata workaround until #1440 lands?
There is precedent in this repo for synthesizing missing
constructors via <add-node>:
source/com.github.bumptech.glide/glide/Transforms/Metadata.xml#L100-L101
injects a public ctor intoGeneratedAppGlideModule, and the
resultingBumptech.Glide.GeneratedAppGlideModule.GeneratedAppGlideModule() -> void
shows up inPublicAPI.Unshipped.txt.source/com.google.android.gms/play-services-iid/Transforms/Metadata.xml#L26-L28
injects an<init>()Vctor onInstanceIDListenerServicewith an
explicitjni-signature="()V", and that surfaces as
Android.Gms.Iid.InstanceIDListenerService.InstanceIDListenerService() -> void.
Applied to SolidColor, the proposed metadata would look like:
<add-node path="/api/package[@name='androidx.compose.ui.graphics']/class[@name='SolidColor']">
<constructor
deprecated="not deprecated" final="false"
name="SolidColor" static="false" visibility="public"
bridge="false" synthetic="false"
jni-signature="(J)V">
<parameter name="value" type="long" jni-type="J" />
</constructor>
</add-node>
The bytecode-level ctor <init>(J)V already exists — the binder just
isn't emitting it because Kotlin metadata flags the param as a
value class. Manually re-introducing it via <add-node> should cause
the generator to bind it as a normal public SolidColor(long value),
the same way the existing applyTo-Pq9zytI(JL…/Paint;F)V and
getValue-0d7_KjU()J methods are bound today (those keep their
hash-mangled JNI names and project the value-class params as their
underlying primitives).
Question for maintainers: is the <add-node> + <constructor> recipe
above an accepted workaround pattern for value-class-param ctors? If so,
it would unblock several hand-rolled-JNI hotspots in
Microsoft.AndroidX.Compose (SolidColor, Color's box-impl,
several Painter shapes) ahead of the eventual #1440 generator fix.
If <add-node> for value-class-param ctors is not expected to work
(e.g. there's a downstream fixup that resurfaces the strip), please say
so here and we'll keep the JNI bridges. Either answer is useful.
Why a separate issue from the existing trackers
| Issue | Scope | Mechanism |
|---|---|---|
dotnet/java-interop#1431 (open) |
Umbrella: inline-class projection + sibling-collision | Generator |
dotnet/java-interop#1432 (merged) |
Phase 1 — sibling-collision dedup for hashed methods | Generator |
dotnet/java-interop#1440 (open) |
Phase 2 — project inline classes as readonly structs |
Generator |
dotnet/android-libraries#1453 (open) |
Stripped TypographyKt.Typography(...) static factory |
Material3 binding + (#1440) |
| This issue | Stripped constructors of types whose Kotlin primary ctor takes a value class | UI.Graphics binding + Metadata.xml |
#1432/#1440/#1431 cover the method side (mangled names like
Button-bWB7cM8). Constructors aren't mangled (they're always <init>),
so the issue presents differently — but the same Kotlin-metadata-driven
strip path drops them. This issue exists so that both halves of the
pattern are tracked, and so the metadata workaround question can be
answered explicitly.
References
jonathanpeppers/Microsoft.AndroidX.Compose#251— real-world consumerBrushBridges.cs— the JNI fallback this issue is asking to removeSolidColor.kt— upstream Kotlin source- Kotlin inline classes — mangling
- Dominant language
- C#
- Stars
- 318
- Forks
- 73
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 10
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from dotnet/android-libraries
-
Difficulty 4/5 3-5 days Newbie friendliness 55/100
dotnet/android-libraries#1503 ·
-
dotnet/android-libraries#1467 · 1 reaction · 2 assignees ·
-
Difficulty 5/5 Over a week Newbie friendliness 45/100
dotnet/android-libraries#1456 · 2 comments · 1 reaction ·
-
area-androidx missing-api
Difficulty 4/5 3-5 days Newbie friendliness 52/100
dotnet/android-libraries#1453 ·
-
dotnet/android-libraries#1444 · 1 reaction · 2 assignees ·
All issues in dotnet/android-libraries
Similar issues
-
type/automation type/tech-debt
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
t/bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
ci-failure-cause test-failure
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
area:auth FE mvp P3
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
klasolsson81/jobbliggaren#1788 ·