Xamarin.Android binding project migration

In .NET, there's no concept of an Android binding project as a separate project type. Any of the MSBuild item groups or build actions that work in Xamarin.Android binding projects are supported through a .NET Android app or library.

To migrate a Xamarin.Android binding library to a .NET Android class library:

  1. In Visual Studio, create a new Android Java Library Binding project with the same name as your Xamarin.Android binding project:

    Screenshot of creating an Android Java Library Binding project in Visual Studio.

    Opening the project file will confirm that you have a .NET SDK-style project:

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <TargetFramework>net8.0-android</TargetFramework>
        <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
      </PropertyGroup>
    </Project>
    

    Note

    The project file for an Android binding library is identical to the project file for an Android class library.

  2. Add your Java Archive (JAR) or Android Archive (AAR) to the project and ensure that its build action is set to AndroidLibrary.

  3. Copy any transforms or additions from your Xamarin.Android bindings library.

Unsupported legacy options

The following legacy options are no longer supported. The supported alternatives have been available for several years, and the smoothest migration option is to update and test your current projects with these options before migrating them to .NET.

AndroidClassParser

jar2xml is no longer a valid option for the $(AndroidClassParser) property. class-parse is now the default and only supported option.

class-parse takes advantage of many new modern features not available in jar2xml, such as:

  • Automatic parameter names for class methods (if your Java code is compiled with javac -parameters).
  • Kotlin support.
  • Static/default interface member (DIM) support.
  • Java Nullable reference type (NRT) annotations support.

AndroidCodegenTarget

XamarinAndroid is no longer a valid option for the $(AndroidCodegenTarget) property. XAJavaInterop1 is now the default and only supported option.

If you have hand-bound code in your Additions files that interacts with the generated binding code, it may need to be updated to be compatible with XAJavaInterop1.

Default file inclusion

Given the following file structure:

Transforms/
    Metadata.xml
foo.jar

Transforms\*.xml files are automatically included as a @(TransformFile) item, and .jar/.aar files are automatically included as a @(AndroidLibrary) item. This will bind C# types for the Java types found in foo.jar using the metadata fixups from Transforms\Metadata.xml.

Default Android related file globbing behavior is defined in AutoImport.props. This behavior can be disabled for Android items by setting the $(EnableDefaultAndroidItems) property to false, or all default item inclusion behavior can be disabled by setting the $(EnableDefaultItems) property to false.

Undesired .jar or .aar files could be included with the default wildcards. For example, the following C# compiler errors results from a AndroidStudio\gradle\wrapper\gradle-wrapper.jar file being unintentionally bound:

Org.Gradle.Cli.AbstractCommandLineConverter.cs(11,89): error CS0535: 'Download' does not implement interface member 'IDownload.Download(URI, File)'
Org.Gradle.Wrapper.Download.cs(10,60): error CS0535: 'AbstractCommandLineConverter' does not implement interface member 'ICommandLineConverter.Convert(ParsedCommandLine, Object)'

To solve this issue, you can remove the specific file in your project file:

<ItemGroup>
  <AndroidLibrary Remove="AndroidStudio\gradle\wrapper\gradle-wrapper.jar" />
</ItemGroup>

Alternatively, you could exclude all files within a folder:

<AndroidLibrary Remove="AndroidStudio\**\*" />

New item group names

<AndroidLibrary> is now the recommended item group to use for all .jar and .aar files. In Xamarin.Android, the following items groups were used, which can instead use item metadata to achieve the same result:

Legacy Item Group New Item Group Item Metadata Legacy Project Type
AndroidAarLibrary AndroidLibrary Bind="false" Application
AndroidJavaLibrary AndroidLibrary Bind="false" Application or class library
EmbeddedJar AndroidLibrary n/a Binding project
EmbeddedReferenceJar AndroidLibrary Bind="false" Binding project
InputJar AndroidLibrary Pack="false" Binding project
LibraryProjectZip AndroidLibrary n/a Binding project

Consider a .aar or .jar file, in which you aren't interested in including a C# binding. This is common for cases where you have Java or Kotlin dependencies that you don't need to call from C#. In this case, you can set the Bind metadata to false. By default, the file is picked up by the default wildcards. You can also use the Update attribute to set the Bind metadata:

<ItemGroup>
  <AndroidLibrary Update="foo.jar" Bind="false">
</ItemGroup>

In an Android class library project, this would redistribute the .jar file inside the resulting NuGet package as is. In an Android application project, this would include the .jar file in the resulting .apk or .aab file. Neither would include a C# binding for this Java library.

Embedded JAR/AAR files

In Xamarin.Android, the Java .jar or .aar was often embedded into the binding .dll as an Embedded Resource. However, this led to slow builds, as each .dll must be opened and scanned for Java code. If found, it must be extracted to disk to be used.

In .NET, Java code is no longer embedded in the .dll. The app build process will automatically include any .jar or .aar files it finds in the same directory as a referenced .dll.

If a project references a binding via <PackageReference> or <ProjectReference> then everything works and no additional considerations are required. However, if a project references a binding via <Reference>, the .jar/.aar must be located next to the .dll. That is, for the following reference:

<Reference Include="MyBinding.dll" />

A directory like the one in the following example won't work:

lib/
    MyBinding.dll

Instead, the directory must also contain the native code:

lib/
    MyBinding.dll
    mybinding.jar

Migration considerations

There are several new features set by default to help produce bindings that better match their Java counterparts. However, if you are migrating an existing binding project, these features may create bindings that are not API compatible with your existing bindings. To maintain compatibility, you may wish to disable or modify these new features.

Interface constants

Traditionally, C# has not allowed constants to be declared in an interface, which is a common pattern in Java:

public interface Foo {
     public static int BAR = 1;
}

This pattern was previously supported by creating an alternative class that contains the constants:

public abstract class Foo : Java.Lang.Object
{
   public static int Bar = 1;
}

With C# 8, these constants are placed on the interface:

public interface IFoo
{
    public static int Bar = 1;
}

However, this means that the alternative class that existing code may depend on is no longer generated.

Setting the $(AndroidBoundInterfacesContainConstants) property to false in your project file will revert to the legacy behavior.

Nested interface types

Traditionally, C# has not allowed nested types to be declared in an interface, which is allowed in Java:

public interface Foo {
     public class Bar { }
}

This pattern was supported by moving the nested type to a top-level type with a generated name composed of the interface and nested type name:

public interface IFoo { }

public class IFooBar : Java.Lang.Object { }

With C# 8, nested types can placed in the interface:

public interface IFoo
{
    public class Bar : Java.Lang.Object { }
}

However, this means that the top-level class that existing code may depend on is no longer generated.

Setting the $(AndroidBoundInterfacesContainTypes) property to false in your project file will revert to the legacy behavior.

If you wish to use a hybrid approach, for example, to keep existing nested types moved to a top-level type, but allow any future nested types to remain nested, you can specify this at the interface level using metadata to set the unnest attribute. Setting it to true will result in "un-nesting" any nested types (legacy behavior):

<attr path="/api/package[@name='my.package']/interface[@name='Foo']" name="unnest">true</attr>

Setting it to false will result in nested types remaining nested in the interface (.NET behavior):

<attr path="/api/package[@name='my.package']/interface[@name='Foo']" name="unnest">false</attr>

Using this approach, you could leave the $(AndroidBoundInterfacesContainTypes) property as true and set unnest to true for every interface with nested types that you currently have. These will always remain top-level types, while any new nested types introduced later will be nested.

Static and default interface members (DIM)

Traditionally, C# has not allowed interfaces to contain static members and default methods:

public interface Foo {
  public static void Bar () { ... }
  public default void Baz () { ... }
}

Static members on interfaces has been supported by moving them to a sibling class:

public interface IFoo { }

public class Foo
{
    public static void Bar () { ... }
}

default interface methods have traditionally not been bound, since they aren't required and there wasn't a C# construct to support them.

With C# 8, static and default members are supported on interfaces, mirroring the Java interface:

public interface IFoo
{
    public static void Bar () { ... }
    public default void Baz () { ... }
}

However, this means the alternative sibling class containing static members will no longer be generated.

Setting the $AndroidBoundInterfacesContainStaticAndDefaultInterfaceMethods property to false in your project file will revert to the legacy behavior.

Nullable reference types

Support for Nullable Reference Types (NRT) was added in Xamarin.Android 11.0. NRT support can be enabled using the standard .NET mechanism:

<PropertyGroup>
  <Nullable>enable</Nullable>
</PropertyGroup>

Because the default for .NET is disable, the same applies for Xamarin.Android projects.

Resource.designer.cs

In Xamarin.Android, Java binding projects didn't support generating a Resource.designer.cs file. Since binding projects are just class libraries in .NET, this file will be generated. This could be a breaking change when migrating existing projects.

One example of a failure from this change, is if your binding generates a class named Resource in the root namespace:

error CS0101: The namespace 'MyBinding' already contains a definition for 'Resource'

Or in the case of AndroidX, there are project files with - in the name such as androidx.window/window-extensions.csproj. This results in the root namespace window-extensions and invalid C# in Resource.designer.cs:

error CS0116: A namespace cannot directly contain members such as fields, methods or statements
error CS1514: { expected
error CS1022: Type or namespace definition, or end-of-file expected

To disable Resource.designer.cs generation, set the $(AndroidGenerateResourceDesigner) property to false in your project file:

<PropertyGroup>
  <AndroidGenerateResourceDesigner>false</AndroidGenerateResourceDesigner>
</PropertyGroup>