Skip to content

Missing Types or Members

Jonathan Pobst edited this page Feb 16, 2024 · 4 revisions

The general philosophy with bindings is: prefer incomplete bindings that are usable over complete bindings that do not compile. That is, if the process hits a case it cannot resolve, it simply omits the type or member.

Most times users only need a few types or members from a library and we would rather them be able to use them immediately than have to fix every single type and member in the library before it compiles.

Of course, there will be times when the type or member that is omitted is one that is needed, and you need to take action in order to help the binding process resolve the issue.

First Steps

Diagnostic MSBuild Log

The first step is absolutely to enable Diagnostic MSBuild logs, and Rebuild (not Build) the project. Most times when a type/member is going to be omitted, a message is written to the diagnostic log telling stating why.

For example, if the type com.example.MyClass is missing, you may see messages like this:

Kotlin: Hiding internal class com.example.MyClass
Error while processing type 'com.example.MyClass': Type 'androidx.fragment.app.FragmentActivity' was not found.

If you see one of these messages for the missing type/member, you now know exactly why it's missing, and can go to Next Steps.

Ensure Jar/Aar Build Action is Correct

If bindings are not being generated for *any* type in your Java library, the issue might be the .jar/.aar is not correctly added to the project. The build action for a library that you want managed bindings for must be set to one of:

  • InputJar
  • EmbeddedJar
  • LibraryProjectZip

If it is set to one of the "reference" build actions like ReferenceJar or EmbeddedReferenceJar, managed bindings will not be generated for the library.

Decompiling the Java Library

Another common issue is that the .jar/.aar may not match the version you expect or match the API documentation you are viewing. For example, you may be looking for API that was added in a version 2.5.0, but your .jar is version 2.4.0.

It's often a good idea to use a Java decompiler on the .jar/.aar to ensure that the type or member you are looking for does indeed exist in the library and that it is public. (Non-public members are generally not bound.)

Next Steps

At this point, you have ensured that:

  • The desired type/member exists in the .jar/.aar an is public. (Using a Java decompiler)
  • The desired type/member does not exist in the generated C# API. (/obj/$(Configuration)/$(TargetFramework)/generated/src)

This means somewhere along the pipeline the bindings process is deciding to omit the type/member. If you found a message from the Diagnostic MSBuild log, you probably have a good idea of where the issue is occurring. The Understanding the Binding Pipeline guide is a helpful introduction to what we're going to be investigating here.

There are outputs created at various points in the binding pipeline that describe exactly what API the process expects to generate at each step. By examining these, you can determine at which point the process decided to omit the desired API.

The examinable artifacts in order are:

  • The input .jar/.aar.
  • /obj/$(Configuration)/$(TargetFramework)/api.xml.class-parse
  • /obj/$(Configuration)/$(TargetFramework)/api.xml
  • /obj/$(Configuration)/$(TargetFramework)/api.xml.fixed
  • The C# output: /obj/$(Configuration)/$(TargetFramework)/generated/src/*.cs

You will want to look at the .xml files to determine when the desired type/member is removed from the API. For example, if the API exists in api.xml.class-parse but not in api.xml then it is getting removed by the Java type resolution phase.

"class-parse"

The first step is called "class-parse". This step is the culprit if:

  • api.xml.class-parse does not contain the desired API

Possible Issues

  • The API is not public in the Java library. This can be verified with the Java decompiler. If the API is not public, there isn't much of a way to work around the issue. The Java API needs to public in order for it to be callable.
  • The API is internal in Kotlin. Java does not have the concept of internal, but Kotlin does. If the Kotlin metadata tells us that the API is not intended to be used outside the package, we hide the API.
    • Example diagnostic message: Kotlin: Hiding internal class com.example.MyClass
    • Fix: Traditionally no fix is available other than modifying the Kotlin code. If the API in internal it is not supposed to be callable. However in the real world things aren't always perfect. In 16.10, we added a way to make internal Kotlin API bindable.

"ApiXmlAdjuster"

The second step is called "ApiXmlAdjuster". This step is the culprit if:

  • api.xml.class-parse contains the desired API
  • api.xml does not contain the desired API

This step generates a java-resolution-report.log in obj\Debug\netX.0-androidXX.0, which will list the types/members that were removed and the reason.

Possible Issues

  • This generally means the API requires a Java type that could not be found, so the API was omitted.
    • Example log message: The class '[Class] com.example.MyClass' was removed because the Java base type 'androidx.fragment.app.FragmentActivity' could not be found.
    • Fix: You will need to ensure the Java type is available.
      • If the needed Java library has a NuGet binding, like Xamarin.AndroidX.Fragment, add a NuGet reference to the needed library.
      • If the library does not have a public binding, you may need to bind the library in its own project first, and then add a project reference to that project to get the needed types.
      • If the library doesn't contain types that need to be bound themselves, the .jar be added as a ReferenceJar or EmbeddedReferenceJar to this project.

Note that there may be a chain of errors that result in the API you are interested in being omitted, and you may need to fix those root issues first.

For example:

The class '[Class] com.example.MyActivityClass' was removed because the Java base type 'com.example.MyBaseActivity' could not be found.
The class '[Class] com.example.MyBaseActivity' was removed because the Java base type 'com.example.MyBaseFragmentActivity' could not be found.
The class '[Class] com.example.MyBaseFragmentActivity' was removed because the Java base type 'androidx.fragment.app.FragmentActivity' could not be found.

In this scenario, MyActivityClass is missing because MyBaseActivity is missing, which is missing because MyBaseFragmentActivity is missing, which is missing because FragmentActivity is missing. Adding a reference to Xamarin.AndroidX.Fragment should fix the root issue, which should fix the entire chain.

"metadata" / "Fixups"

The third step is called "metadata" or "fixups". This step is the culprit if:

  • api.xml contains the desired API
  • api.xml.fixed does not contain the desired API

Possible Issues

This steps is simply applying the metadata.xml transforms that you have defined. There shouldn't be any API that this step removes automatically.

If API is removed by this step, it is because you have used <remove-node> in metadata, like:

<remove-node path="/api/package[@name='com.example']/class[@name='MyClass']" />

If this was not intended, remove the unintended <remove-node> element.

"generator"

The final step is called "generator". This step is the culprit if:

  • api.xml.fixed contains the desired API
  • The C# output (/obj/$(Configuration)/$(TargetFramework)/generated/src/*.cs) does not contain the desired API

Possible Issues

This step can have many possible causes, so finding the message in the Diagnostic MSBuild log is generally very important. Most issues from this step are actually surfaced as build warnings, making them easier to find.

An example:

Warning BG8401 Skipping 'com.example.MyClass.Builder' due to a duplicate nested type name. (Java type: 'com.example.MyClass.Builder')

In this example, there may be a conflict between a field named BUILDER (which will be transformed to Pascal case Builder) and a nested type named Builder. Because both cannot exist in the same type, one will be omitted. This could be fixed by renaming the field to be BuilderField:

<attr path="/api/package[@name='com.example']/class[@name='MyClass']/field[@name='BUILDER']" name="managedName">BuilderField</attr>

Some of these cases are outlined in detail in the Common Binding Issues guide.