-
Notifications
You must be signed in to change notification settings - Fork 53
Missing Types or Members
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.
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.
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.
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.)
At this point, you have ensured that:
- The desired type/member exists in the
.jar
/.aar
an ispublic
. (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.
The first step is called "class-parse". This step is the culprit if:
-
api.xml.class-parse
does not contain the desired API
- The API is not
public
in the Java library. This can be verified with the Java decompiler. If the API is notpublic
, there isn't much of a way to work around the issue. The Java API needs topublic
in order for it to be callable. - The API is
internal
in Kotlin. Java does not have the concept ofinternal
, 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 makeinternal
Kotlin API bindable.
- Example diagnostic message:
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.
- 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 aReferenceJar
orEmbeddedReferenceJar
to this project.
- Example log message:
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.
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
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.
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
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.