Kotlin `object` companion field not surfaced on outer class binding (KeyboardOptions.Companion, TextStyle.Companion, ...)
Los mantenedores suelen responder en 2 días
@jonathanpeppers ya está trabajando en esto.
Desde el 10/6/2026.
- #1468 de @copilot-swe-agent — abierto
Evaluación
Este issue todavía no se ha evaluado.
Descripción
Summary
Kotlin object companion fields aren't surfaced as public properties on the outer class binding, so consumers can't reach the singleton through the binding alone. They have to fall back to raw JNI (FindClass + GetStaticFieldID("Companion") + GetStaticObjectField) to bootstrap the companion peer.
Repro
Pick any AndroidX type with a Kotlin object companion. androidx.compose.foundation.text.KeyboardOptions is the case I just hit:
public class KeyboardOptions /* ... */ {
public companion object {
public val Default: KeyboardOptions = KeyboardOptions(/* ... */)
}
}
Kotlin lowers that to bytecode:
public final class androidx.compose.foundation.text.KeyboardOptions {
public static final androidx.compose.foundation.text.KeyboardOptions$Companion Companion;
public static final androidx.compose.foundation.text.KeyboardOptions$Companion access$getCompanion$cp();
...
}
After binding through Xamarin.AndroidX.Compose.Foundation 1.11.2.2:
AndroidX.Compose.Foundation.Text.KeyboardOptions— bound (sealed class)KeyboardOptions.Companionnested type — bound with[Register("androidx/compose/foundation/text/KeyboardOptions$Companion", DoNotGenerateAcw = true)]Companion.Default— bound (instance getter, returnsKeyboardOptions)KeyboardOptions.Copy(int, Boolean?, int, int, ...)— bound
What's missing:
- No
KeyboardOptions.Companionstatic property/field accessor on the outer class. Decompiled the binding's outerKeyboardOptionsand theCompanionfield is dropped — there's nopublic static KeyboardOptions.Companion Companion { get; }member to call. - No public ctor (stripped —
@JvmInline value classparams hit the-d9O1mEEmangling). - No
KeyboardOptionsKtstatic helper.
So even though the entire downstream API (Companion.Default, Copy(...)) is bound, there's no binding-only way to get the Companion instance to start the call chain.
Current workaround
Hand-roll the singleton bootstrap once per affected type. Verbatim from Microsoft.AndroidX.Compose/src/Microsoft.AndroidX.Compose/KeyboardOptionsCompanion.cs:
IntPtr cls = JNIEnv.FindClass("androidx/compose/foundation/text/KeyboardOptions");
IntPtr field = JNIEnv.GetStaticFieldID(cls, "Companion",
"Landroidx/compose/foundation/text/KeyboardOptions$Companion;");
IntPtr local = JNIEnv.GetStaticObjectField(cls, field);
s_companion_ref = JNIEnv.NewGlobalRef(local);
JNIEnv.DeleteLocalRef(local);
return new Companion(s_companion_ref, JniHandleOwnership.DoNotTransfer);
Everything downstream (Default, Copy(...)) is the binding — only the static-field read needs JNI. We already have the same workaround for androidx.compose.ui.text.TextStyle.Companion (TextStyleCompanion.cs) and need it for several more (ContentScale.Companion, Alignment.Companion, FontWeight.Companion, etc.).
Proposed fix
Single Metadata.xml fix-up per androidx.compose.* transform that has a Kotlin object companion. For KeyboardOptions:
<!-- androidx.compose.foundation/Transforms/Metadata.xml -->
<attr path="/api/package[@name='androidx.compose.foundation.text']/class[@name='KeyboardOptions']/field[@name='Companion']"
name="visible">true</attr>
After which the binder should emit public static KeyboardOptions.Companion Companion { get; } on the outer class, and consumers can write KeyboardOptions.Companion.Default directly. We'd then delete the hand-rolled JNI bootstrap files.
Possible complication
The binder sometimes hides Companion fields because the property name collides with the nested type name (you end up with two Companion members at the same scope — one type, one property). C# handles that fine, but the generator may have proactively dropped the field to avoid the conflict. If that's the cause, the fix becomes either:
<attr ... name="managedName">CompanionInstance</attr>to rename the property, or<remove-node>+<add-node>injecting an explicit property body.
Both still live in Metadata.xml — no generator code change needed.
Affected packages
Every Compose package that exposes a Kotlin object companion. Most-impactful ones I've already had to bootstrap by hand or know I'll need to:
Xamarin.AndroidX.Compose.Foundation—KeyboardOptions.CompanionXamarin.AndroidX.Compose.UI.Text—TextStyle.Companion,TextDecoration.Companion,FontWeight.Companion,FontFamily.Companion,FontStyle.CompanionXamarin.AndroidX.Compose.UI—Alignment.Companion,Modifier.Companion,ContentScale.CompanionXamarin.AndroidX.Compose.UI.Graphics—Color.Companion,Shape.CompanionfamilyXamarin.AndroidX.Compose.Material3—MaterialTheme.Companion, many of the*Defaultstypes
Not Compose-specific — the same gap should exist on any Kotlin-authored AndroidX type with an object companion.
Related
jonathanpeppers/Microsoft.AndroidX.Compose#246— most recent occurrence (KeyboardOptionsCompanion.cs).TextStyleCompanion.csin the same repo — first occurrence; we hit the identical pattern in Phase 1 and shipped the same JNI workaround.
- Lenguaje dominante
- C#
- Estrellas
- 319
- Forks
- 73
- Merge medio
- 2 d 11 h
- PR fusionados (30 d)
- 8
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de dotnet/android-libraries
-
Unfreeze Xamarin.Firebase.Firestore and Xamarin.Protobuf.JavaLite now that Firebase BoM 35.0.0 fixes the Protolite conflictPosiblemente ocupada @jonathanpeppers la tomó hace 10 días. Abierto
dotnet/android-libraries#1563 · 1 reacción · 2 asignados ·
Los mantenedores suelen responder en 2 días
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 20/100
dotnet/android-libraries#1561 · 4 comentarios ·
Los mantenedores suelen responder en 2 días
-
Regression: incorrect nullability annotations in Xamarin.AndroidX.AppCompat 1.8.0Posiblemente ocupada @jonathanpeppers la tomó hace 13 días. Abierto
Dificultad 4/5 3-5 días Aptitud para principiantes 40/100
dotnet/android-libraries#1558 · 2 comentarios · 2 reacciones · 2 asignados ·
Los mantenedores suelen responder en 2 días
-
Dificultad 4/5 3-5 días Aptitud para principiantes 68/100
dotnet/android-libraries#1549 ·
Los mantenedores suelen responder en 2 días
-
Dificultad 4/5 3-5 días Aptitud para principiantes 55/100
dotnet/android-libraries#1503 ·
Los mantenedores suelen responder en 2 días
Todos los issues de dotnet/android-libraries
Issues similares
-
area-multitenancy area-security p1-recommended priority-high technical-debt
Dificultad 2/5 Menos de una hora Aptitud para principiantes 75/100
Los mantenedores suelen responder en 1 día
-
kind/enhancement Provider Upgrade pulumi/pulumi-azure R1 Task
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
pulumi/pulumi-azure#3669 ·
-
type/bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
Los mantenedores suelen responder en 1 día
-
webclient does not startPosiblemente ocupada @PawelTroka la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
PawelTroka/Computator.NET#257 ·
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
eriknihlen/OpenAC#255 ·
Los mantenedores suelen responder en 1 día