jpobst / prototype.android.mavenbindings Goto Github PK
View Code? Open in Web Editor NEWPrototype of MSBuild tasks to facilitate binding of Java libraries from Maven repositories
License: MIT License
Prototype of MSBuild tasks to facilitate binding of Java libraries from Maven repositories
License: MIT License
One of the key tenets of "slim bindings" is that you only bind the type(s)/method(s) that you need to use. Unfortunately this does not work for a normal binding project because your desired types likely derive from other unbound types or take unbound types as method parameters or return types.
Example:
public class MapView extends MapViewBase implements MappableComponent {
public MapView (Activity activity, MapViewOptions options) { ... }
public PinResult AddPin (string name, PinCoordinates coordinates) { ... }
}
Trying to bind just MapView
would fail because it also needs the types: MapViewBase
, MappableComponent
, MapViewOptions
, PinResult
, and PinCoordinates
. Binding each of those types may require further types, and you essentially end up needing to bind the entire library.
The way slim bindings fixes this is that you write a Java wrapper around the API you need that does not expose these custom types to C#. This may look something like:
public class MyMapView extends android.view.View {
private MapView mv;
public MyMapView (Activity activity, string option1, bool option2) {
var options = new MapViewOptions (option1, option2);
mv = new MapView (activity, options);
}
public bool AddPin (string name, long latitude, long longitude) {
var result = mv.AddPin (name, new PinCoordinates (latitude, longitude);
return result.Success;
}
}
In effect, it performs "type erasure" of custom types in order to bind the class.
One potential downside to this is that users must write a Java wrapper in order to expose the correct API, and many users may not know Java or would prefer to remain in C#.
jingen
BindingsEither as a conscience choice or because it is very early in development, jnigen
does not expose any custom types in their bindings. Every non-primitive type is exposed as jni.JObject
. (Their ~equivalent of Java.Lang.Object
.)
So their binding of the above class would look like this (in a C#-looking language rather than dart):
public class MyMapView : Java.Lang.Object {
public MyMapView (Java.Lang.Object activity, Java.Lang.Object options) { ... }
public Java.Lang.Object AddPin (string name, Java.Lang.Object coordinates) { ... }
}
Basically, they are performing the same "type erasure" that our slim bindings require.
Applying the automatic type erasure that jnigen
does to our generator
process would provide a nice "hybrid" approach:
JLO
would eliminate nearly all errors that prevent typed bindings from automatically working today:
override
signaturesThe downside is that you lose all C#/compile time type checking and type checks are performed at runtime by Java. For this reason, it feels better to keep these as "slim bindings" that are used sparingly on a few types rather than binding an entire library without types.
Example:
// Works correctly:
var activity = my_activity;
var map_view = new MapView (activity, null);
// Compiles, but throws a Java exception like IncompatibleClassException or something at runtime
var context = my_activity.Context;
var map_view = new MapView (context, null);
We would need a way to specify which types to bind, perhaps in MSBuild?
<ItemGroup>
<AndroidMavenLibrary Include="map.company:MyMaps" Version="1.0.0" />
<AndroidBindType Include="map.company.MyMapView" />
</ItemGroup>
Theoretically this is all that should be required to create a vaguely typed binding for the MyMapView
class.
Flutter also includes the ability to exclude problematic members if needed, which could be something like:
<AndroidBindExcludeMember Include="map.mycompany.PinResult map.mycompany.MyMapView.AddPin (string, map.mycompany.PinCoordinates)" />
Or it could use XPath.
Inevitably you will need to work with some additional "real" types from a library. Consider the AddPin
method, which requires a PinCoordinates
parameter and returns a PinResult
type.
Assuming they do not come from a typed binding of a dependency, we would need to additionally bind them:
<AndroidBindType Include="map.company.PinCoordinates" />
<AndroidBindType Include="map.company.PinResult" />
We can now create instances of these types, or use JavaCast<T>
to convert JLO
to a type:
public void DoMapStuff (MyMapView map)
{
var coords = new PinCoordinates (0, 0);
var result = map.AddPin ("Null Island", coords).JavaCast<PinResult> ();
Console.WriteLine ("Adding pin was successful: " + result.Success.ToString ());
}
Hey,
I'm trying to use this with this: https://central.sonatype.com/artifact/org.jboss/jboss-vfs/3.2.17.Final but the ".Final" is causing the following:
The "MavenDependencyVerifierTask" task failed unexpectedly.
System.ArgumentException: '3.2.17.Final' is not a valid version string.
Parameter name: value
at NuGet.Versioning.NuGetVersion.Parse(String value)
at MavenNet.MavenVersionRange.Satisfies(String version)
at System.Linq.Enumerable.Any[TSource](IEnumerable`1 source, Func`2 predicate)
at Prototype.Android.MavenBinding.Tasks.DependencyResolver.IsDependencySatisfied(Dependency dependency, MicrosoftNuGetPackageFinder packages, LogWrapper log)
at Prototype.Android.MavenBinding.Tasks.MavenDependencyVerifierTask.Execute()
at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
at Microsoft.Build.BackEnd.TaskBuilder.<ExecuteInstantiatedTask>d__26.MoveNext() Tesseract.Binding.Droid C:\Users\kfrancis\.nuget\packages\xamprototype.android.mavenbinding.tasks\0.0.7\build\XamPrototype.Android.MavenBinding.Tasks.targets
They seem to include that in all their versions: https://central.sonatype.com/artifact/org.jboss/jboss-vfs/3.2.17.Final/versions
Another potential way to implement "bind only what you need" would be to remain fully in C# and place the burden on the user to provide the metadata needed to perform JNI interop. While this can be cumbersome and tricky for lots of API, for a few small uses it might be acceptable.
Downsides:
Open questions:
// Create a new "com.googleplay.services.ApiClient" type instance
var api_client = new UntypedJavaObject ("com.googleplay.services", "ApiClient");
// or with constructor parameters
var api_client = new UntypedJavaObject ("com.googleplay.services", "ApiClient", "my_api_key");
// Invoke a method with 'void' return
api_client.InvokeMethod ("setApiKey", "my_api_key");
// Invoke a method with 'int' return
var quota = api_client.InvokeMethod<int> ("getRemainingQuota");
// Get an 'int' field
var quota = api_client.GetField<int> ("API_VERSION");
// Set an 'int' field
api_client.SetField ("API_VERSION", 34);
Would also need a static
version:
var api_client = new UntypedStaticJavaObject ("com.googleplay.services", "ApiClient");
var quota = api_client.GetField<int> ("API_VERSION");
Java.Lang.Object
JLO
as an "untyped" parameter typevar view = FindViewById (Resources.layout);
var map = new UntypedJavaObject ("com.mapbox.maps", "MapView");
map.InvokeMethod ("attachToView", view);
JLO
from an "untyped" return typevar map = new UntypedJavaObject ("com.mapbox.maps", "MapView");
var view = map.InvokeMethod<Android.Views.View> ("getAttachedView");
JLO
that was bound as Object
to an "untyped" typevar view = FindViewById (Resources.layout);
var unknown_jlo = view.GetThing ();
var untyped = UntypedJavaObject.FromObject (view);
JLO
var map = new UntypedJavaObject ("com.mapbox.maps", "MapView");
var untyped = map.InvokeMethod<UntypedJavaObject> ("getAttachedView");
var view= untyped.JavaCast<Android.Views.View> ();
JLO
var view = FindViewById (Resources.layout);
var untyped = UntypedJavaObject.FromObject (view);
untyped.InvokeMethod ("newUnboundMethod");
Converting the sample from https://github.com/Redth/MapboxSlimBindingDemo/tree/main:
public class MainActivity : Activity
{
protected override void OnCreate (Bundle? savedInstanceState)
{
base.OnCreate (savedInstanceState);
// Create new MapView ('this' is 'Android.Content.Context')
var mapview = new UntypedJavaObject ("com.mapbox.maps", "MapView", this);
// MapView inherits Android.Views.View
var view = mapview.JavaCast<Android.Views.View> ();
// Set as our view
SetContentView (view);
}
}
There seems to be an additional way to specify a parent POM we need to investigate:
https://repo1.maven.org/maven2/com/squareup/wire/wire-runtime/4.7.1/wire-runtime-4.7.1.pom
That is, this POM does not specify a <parent>
section like our previous examples did:
https://repo1.maven.org/maven2/com/google/guava/guava/31.1-android/guava-31.1-android.pom
<parent>
<groupId>com.google.guava</groupId>
<artifactId>guava-parent</artifactId>
<version>31.1-android</version>
</parent>
Instead, it specifies this which I think we need to add support for:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio-bom</artifactId>
<version>3.3.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
This probably allows multiple parent POMs to be specified?
So, I've long been searching for a way to create bindings without the annoying step of interacting with maven to get the aar file, so this is AMAZING.
That being said, try creating one for intercom. The SDK Base, for example, has a very large set of runtime dependencies so it's quite tedious setting that up: https://mvnrepository.com/artifact/io.intercom.android/intercom-sdk-base/14.2.0
So far, the base csproj is starting to look like this and I'm curious if I'm doing this right:
<ItemGroup>
<PackageReference Include="GoogleGson" Version="2.10.1.1" />
<PackageReference Include="Square.OkHttp3" Version="4.10.0.1" />
<PackageReference Include="Xamarin.AndroidX.Annotation" Version="1.6.0" />
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.6.1" />
<PackageReference Include="Xamarin.AndroidX.ConstraintLayout" Version="2.1.4.3" />
<PackageReference Include="Xamarin.AndroidX.Core.Core.Ktx" Version="1.9.0.2" />
<PackageReference Include="Xamarin.AndroidX.DataBinding.ViewBinding" Version="7.4.2" />
<PackageReference Include="Xamarin.AndroidX.ExifInterface" Version="1.3.6" />
<PackageReference Include="Xamarin.AndroidX.Fragment.Ktx" Version="1.5.6" />
<PackageReference Include="Xamarin.AndroidX.RecyclerView" Version="1.3.0" />
<PackageReference Include="Xamarin.AndroidX.VectorDrawable" Version="1.1.0.16" />
<PackageReference Include="Xamarin.AndroidX.VectorDrawable.Animated" Version="1.1.0.16" />
<PackageReference Include="Xamarin.AndroidX.WebKit" Version="1.6.1" />
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.8.0" />
<PackageReference Include="Xamarin.Kotlin.StdLib" Version="1.8.20" />
<PackageReference Include="Xamarin.KotlinX.Coroutines.Core" Version="1.6.4.2" />
<PackageReference Include="XamPrototype.Android.MavenBinding.Tasks" Version="0.0.7" />
</ItemGroup>
<ItemGroup>
<AndroidMavenLibrary Include="io.intercom.android:intercom-sdk-base" Version="14.2.0" />
<AndroidMavenLibrary Include="io.intercom.android:intercom-sdk-ui" Version="14.2.0" />
<AndroidMavenLibrary Include="com.squareup.okhttp3:okhttp" Version="4.9.3" Bind="false" />
<AndroidMavenLibrary Include="org.jetbrains.kotlin:kotlin-parcelize-runtime" Version="1.8.20" Bind="false" />
<AndroidMavenLibrary Include="org.jetbrains.kotlin:kotlin-android-extensions-runtime" Version="1.5.20" Bind="false" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Intercom.Droid.Commons\Intercom.Droid.Commons.csproj" JavaArtifact="com.intercom:android-commons" JavaVersion="2.0.0" />
<ProjectReference Include="..\Intercom.Droid.Composer.Gallery\Intercom.Droid.Composer.Gallery.csproj" JavaArtifact="com.intercom:android-composer-gallery" JavaVersion="3.3.3" />
<ProjectReference Include="..\Intercom.Droid.Composer\Intercom.Droid.Composer.csproj" JavaArtifact="com.intercom:android-composer" JavaVersion="3.3.3" />
</ItemGroup>
For example, if I add one that it's complaining about:
Severity Code Description Project File Line Suppression State Priority
Error Maven dependency 'com.google.android.flexbox:flexbox' version '3.0.0' is not satisfied. Intercom.Droid.SDK.Base C:\Users\kfrancis\.nuget\packages\xamprototype.android.mavenbinding.tasks\0.0.7\build\XamPrototype.Android.MavenBinding.Tasks.targets 28 Normal
as <AndroidMavenLibrary Include="com.google.android.flexbox:flexbox" Version="3.0.0" Bind="false" />
then I get the error:
Severity Code Description Project File Line Suppression State Priority
Error Cannot download artifact 'com.google.android.flexbox:flexbox'.
- com.google.android.flexbox_flexbox.jar: Response status code does not indicate success: 404 (Not Found).
- com.google.android.flexbox_flexbox.aar: Response status code does not indicate success: 404 (Not Found). Intercom.Droid.SDK.Base C:\Users\kfrancis\.nuget\packages\xamprototype.android.mavenbinding.tasks\0.0.7\build\XamPrototype.Android.MavenBinding.Tasks.targets 22 Normal
AFAIK, that entry should be correct based on the maven information: https://mvnrepository.com/artifact/com.google.android.flexbox/flexbox/3.0.0
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.