Description
Background
The MAUI Community Toolkit provides guidance on creating "slim bindings" called "Native Method Interop". There are a few downsides to this approach:
- It requires non-trivial Java knowledge to set up Android Studio, a Java Binding project, and write a Java wrapper API.
- Integrating the wrapped
.jar
and potentially its dependencies into the managed app can be tricky, getting its versions to align with.jar
files pulled in through NuGet packages.
Once we're already down the path of asking users to write the APIs by hand, could we use Roslyn source generators to allow them to do the same in C#?
Note
A very quick prototype and sample is available here.
Example
Using the example from MAUI Community Toolkit, we could allow the user to write the C# API that matches the Java API to bind:
// Java API to bind
package com.facebook;
public class FacebookSdk {
public final void setIsDebugEnabled (Boolean value) { ... }
}
// C# user writes
[JavaBindingType ("com.facebook.FacebookSdk")]
public partial class FacebookSdk : Java.Lang.Object
{
[JavaBindingMethod ("setIsDebugEnabled")]
public partial void SetIsDebugEnabled (bool value);
}
We could then use a Roslyn source generator to fill in the plumbing in a generated partial class:
// Additional C# that gets generated
[global::Android.Runtime.Register ("com/facebook/FacebookSdk", DoNotGenerateAcw=true)]
public partial class FacebookSdk
{
[Register ("setIsDebugEnabled", "(Z)V", "")]
public partial unsafe void SetIsDebugEnabled (bool value)
{
const string __id = "setIsDebugEnabled.(Z)V";
try {
JniArgumentValue* __args = stackalloc JniArgumentValue [1];
__args [0] = new JniArgumentValue (nonRoot);
_members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args);
} finally {
}
}
}
Vague Typing
This proposal could also incorporate the principles of Vaguely Typed Slim Bindings, allowing the user to bind as much or as little as they want.
Imagine the Java API:
public class Map {
public final MapPin addPin (int x, int y) { ... }
}
If the user just wants to add the map pin and isn't interested in using the return value, they can bind it as Java.Lang.Object
:
[JavaBindingType ("com.mapsui.Map")]
public partial class Map : Java.Lang.Object
{
[JavaBindingMethod ("addPin")]
public partial Java.Lang.Object AddPin (int x, int y);
}
If they want to interact with the MapPin
type, they can provide as much of it as they want:
[JavaBindingType ("com.mapsui.Map")]
public partial class Map : Java.Lang.Object
{
[JavaBindingMethod ("addPin")]
public partial MapPin AddPin (int x, int y);
}
[JavaBindingType ("com.mapsui.MapPin")]
public partial class MapPin : Java.Lang.Object
{
[JavaBindingMethod ("get_X")]
public partial int GetX ();
[JavaBindingMethod ("get_Y")]
public partial int GetY ();
}
Interaction with Existing Bindings
These bindings should compose well with existing bound libraries. Imagine a MapActivity
that subclasses androidx.activity.Activity
. Adding the Xamarin.AndroidX.Activity
NuGet package would allow your bound type to easily subclass the "real" class:
[JavaBindingType ("com.mapsui.MapActivity")]
public partial class MapActivity : Xamarin.AndroidX.Activity
{
...
}
Downsides
The largest downside is likely the lack of compile-time checking. If a user misspells a Java type or method name, we won't have tooling that can verify it, so they will receive a runtime error. Same if the user provides incorrect parameter or return types, or provides the wrong number of parameters.