July 29th, 2024

Creating Bindings for .NET MAUI with Native Library Interop

Rachel Kang (SHE/HER)
Product Manager

In today’s app development landscape, the ability to extend .NET applications by leveraging native capabilities is invaluable. The .NET MAUI handler architecture empowers developers to directly manipulate native controls with .NET code, even allowing for the seamless creation of cross-platform custom controls. Yet, the potential extends beyond just native platform APIs. What if you could also tap into native library APIs, unlocking even more possibilities?

Native Library Interop for .NET MAUI, previously known as the Slim Binding approach, is an alternative method for integrating native libraries into .NET MAUI applications, including .NET for Android, .NET for iOS, and .NET for Mac Catalyst. This approach enables direct access to native library APIs in a way that is both streamlined and maintenance-friendly, eliminating the need to bind entire libraries through traditional methods.

You may be asking yourself, what is a Binding? When you want to use a third-party iOS or Android library not written in C#, you need a way to consume it in your .NET MAUI application. This is where Binding Projects come in enabling you to create a C# API definition to describe how the native API is exposed in .NET, and how it maps to the underlying library. After you establish this definition, you compile it to generate a “binding” assembly that can be utilized within your .NET MAUI application. This process mirrors the functionality of .NET for iOS and Android; when you use a native iOS or Android API in C#, it’s accessible due to the bindings created for the core APIs.

The Maui.NativeLibraryInterop repository serves as a valuable resource of community-curated samples, offering .NET developers an opportunity to delve into and benefit from shared knowledge, as well as contribute their own insights. With a ready-to-use template for creating new bindings, it serves as an excellent foundation for developers embarking on their journey from concept to execution. The great part about Native Library Interop is that it is a more general way of creating bindings, not limited to just binding libraries, and can technically be used to tap deeper into the native platform SDKs.

In this post, I am excited to share my own journey with Native Library Interop for .NET MAUI, presenting a practical example to illustrate how this innovative approach can be leveraged in your .NET MAUI applications. Join me as I implement a binding, using the template and following the guidance in the Getting Started documentation.

Getting started with the Native Library Interop template

To get started, I first cloned the Maui.NativeLibraryInterop repository. If interested in building off one of the existing bindings samples (Facebook, Firebase, GoogleCast), I would start from the samples contained in the respective folders. As I am interested in creating a binding from a completely different library, however, I’m going to start with the template! The template contains the foundation for getting started with using Native Library Interop to create an Android binding, iOS and Mac Catalyst binding, and a .NET MAUI sample app that uses both.

Getting the prerequisites

Before proceeding, make sure you have all the prerequisites installed. If you are a long-time .NET MAUI developer, it is likely you already have most if not all of them installed like myself, but be sure to check the full list of prerequisites.

I copy and paste the template into my desired location, deploy the sample to all three platforms, and tada! Everything works as expected. I see “Hello, Community Toolkit from java!” on the Android app, demonstrating the Android binding working as expected, and “Hello, Community Toolkit from swift!” on the iOS and Mac Catalyst apps, demonstrating the MaciOS binding working as expected.

Native Library Interop

What will I bind?

So what am I looking to bind? Well, I want to include a nice pie chart in my app! And the .NET MAUI SDK does not currently have a built-in control for that.

While all the charts I can create using the Charts library are so beautiful, I have chosen the Native Library Interop approach because I only need a pie chart in my .NET MAUI app right now, so I want to bind the APIs I need just for the pie chart, and nothing more.

To create my binding for charts, I will be using the MPAndroidChart library for Android and the equivalent Charts library for iOS and Mac Catalyst.

As such, I would like the name of my binding to reflect that. For Android, I rename the class, file name, and all references of DotnetNewBinding at android/native/newbinding/src/main/java/com/example/newbinding/DotnetNewBinding.java. For MaciOS, I do the same for macios/native/NewBinding/NewBinding/DotnetNewBinding.swift. While optional, I took the liberty to rename all folders, files, and instances of “newBinding” to “charts” in my project as well.

Cool, easy enough. What’s next?

Setting up the .NET binding libraries

I’m planning on binding libraries for Android, iOS, and Mac Catalyst, and I’m lucky to have the option to support all three with the libraries I found! If not interested in all platforms, I’d simply delete the folder, target framework, and references for the one I’m not interested in.

As for the .NET version, I’m going to stick with using .NET 8 for now. When I’m ready to use .NET 9, however, I will eventually update the TargetFrameworks and versions in Charts.MaciOS.Binding.csproj and Charts.Android.Binding.csproj respectively.

And that’s it! While I have the choice to customize here, I did not need to take any extra steps to set up the .NET binding libraries beyond what the template has already set up for me.

Setting up the native wrapper projects and libraries

Now, let’s make sure the same is reflected in the native projects, and bring in the native libraries!

iOS & Mac Catalyst

First, I open the native project macios/native/Charts/Charts.xcodeproj in Xcode. I check in Targets > General that the supported destinations and iOS version match what I need, so once again, I’m already good to go here.

Xcode Supported Destinations

Now, it’s time to bring in the native Charts library! As there are various options for bringing in a native library, this step will vary based on what works best with the specific library and personal preferences. In my case, I’m going to opt to use Swift Package Manager by navigating to File > Add Package Dependencies…

Add package dependencies

searching for the Charts library package,

Add package

and clicking Add Package. The Charts library has been added to my native Xcode project!

Android

Now, it’s time to do the same thing in Android land! First, I open the native project android/native in Android Studio. Once the project loads, I open build.gradle.kts (:charts) and confirm that the compileSdk version reflects my needs.

Now, to bring in the native Charts library, I make the following edits in build.gradle.kts:

dependencies {

    // Add package dependency for binding library
    implementation("com.github.PhilJay:MPAndroidChart:v3.1.0")

    // Copy dependencies for binding library
     "copyDependencies"("com.github.PhilJay:MPAndroidChart:v3.1.0")
}

I also add the relevant maven repository in settings.gradle.kts:

dependencyResolutionManagement {
    ...
    repositories {
        ...
        // Add repository here
        maven { url = uri("https://jitpack.io") }
    }
}

Last but not least, I click on the Sync Project with Gradle Files button in the upper-right-hand corner to make the cute gradle elephant happy.

Creating the API interface

Now that we have brought in the native libraries, it’s time to build the APIs we will be using in our .NET apps! The cool part of the Native Library Interop approach is that all of this happens on the native side. This means we can leverage any existing documentation that the libraries provide to write in the native languages directly – Swift / Objective-C for iOS and Mac Catalyst, and Java / Kotlin for Android. It also means we can update those APIs a lot more easily with less of the overhead that comes with manually translating everything into .NET terms.

iOS & Mac Catalyst

In DotnetCharts.swift, I define all the APIs my heart desires. While that quite literally means I can define any API in Swift, as the template string example also shows, I will stay focused for now on my mission of creating an API interface for Charts, and will import DGCharts at the top of the file.

Then, I write up the API definition for creating a pie chart. As a .NET developer, I can’t say I’m the biggest Swift expert, but being able to leverage the Swift samples directly from the Charts library repo, and getting assistance from GitHub Copilot is certainly a game changer that makes this part so much less daunting.

Once I ensure my Swift code is valid by building the Xcode project successfully, I run back as quickly as I can to the .NET side of things to make sure the native library indeed interops.

From macios/Charts.MaciOS.Binding I run dotnet build. This generates the .NET API definitions in macios/native/Charts/bin/Release/net8.0-ios/sharpie/Charts/ApiDefinitions.cs which I then copy into charts/macios/Charts.MaciOS.Binding/ApiDefinition.cs.

Then, I run dotnet build once more to ensure everything is happy. 🙂

MaciOS binding dotnet build succeeded

Android

Back again now in Android world! In DotnetCharts.java, I can define any API in Java, as the template string examples shows here as well. To stay focused on Charts, however, I will import everything I need. While the libraries are pretty parallel, they are implemented slightly differently, which will affect how I import and define my APIs here as well.

As such, I import the following from com.github.mikephil.charting:

import com.github.mikephil.charting.charts.PieChart;
import com.github.mikephil.charting.data.PieData;
import com.github.mikephil.charting.data.PieDataSet;
import com.github.mikephil.charting.data.PieEntry;
import com.github.mikephil.charting.utils.ColorTemplate;

Then, I once again write up the API definition for creating a pie chart. Just as I’m not the biggest Swift expert, I am also not the biggest Android expert… but I am still a mobile app developer! Being able to directly leverage online resources and GitHub Copilot makes this all very feasible. 🙂

Running back again to the comfort of .NET, I navigate to android/Charts.Android.Binding and run dotnet build.

Android binding dotnet build succeeded

This generates a copy of the dependencies at android/native/charts/bin/Release/net8.0-android/outputs/deps/MPAndroidChart-v3.1.0.aar which, unlike with iOS and Mac Catalyst, I will need to directly reference in my .NET sample app by adding the following to MauiSample.csproj:

<!-- Reference the Android binding dependencies -->
<ItemGroup Condition="$(TargetFramework.Contains('android'))">
    <AndroidLibrary Include="..\android\native\charts\bin\Release\net8.0-android\outputs\deps\MPAndroidChart-v3.1.0.aar">
        <Bind>false</Bind>
        <Visible>false</Visible>
    </AndroidLibrary>
</ItemGroup>

Consuming the APIs in your .NET app

It’s time for the moment of truth! The Charts binding can now be used in any new or existing .NET MAUI apps, including any .NET for iOS, .NET for Mac Catalyst, and .NET for Android apps. For simplicity, I will use it in the .NET MAUI sample app that came with the template which already references the .NET binding libraries for me in the MauiSample.csproj:

<!-- Reference to MaciOS Binding project -->
<ItemGroup Condition="$(TargetFramework.Contains('ios')) Or $(TargetFramework.Contains('maccatalyst'))">
    <ProjectReference Include="..\macios\Charts.MaciOS.Binding\Charts.MaciOS.Binding.csproj" />
</ItemGroup>

<!-- Reference to Android Binding project -->
<ItemGroup Condition="$(TargetFramework.Contains('android'))">
    <ProjectReference Include="..\android\Charts.Android.Binding\Charts.Android.Binding.csproj" />
</ItemGroup>

In MainPage.xaml.cs, I import ChartsMaciOS.DotnetCharts and ChartsAndroid.DotnetCharts and use platform directives to directly leverage the APIs I created, just as I would with any other platform-specific implementation in .NET MAUI.

public class MauiPieChart : View
{
    public List<PieChartSlice> Slices { get; set; } = new List<PieChartSlice>();
}

public class PieChartSlice
{
    public string Name { get; set; } = string.Empty;

    public int Count { get; set; }

    public Color Color
    {
        get => _color ??= GenerateRandomColor();
        set => _color = value;
    }

    private Color? _color = null;

    private Color GenerateRandomColor()
    {
        Random random = new Random();
        return new Color(random.Next(256), random.Next(256), random.Next(256));
    }
}

public partial class MauiPieChartHandler
{
    public static IPropertyMapper<MauiPieChart, MauiPieChartHandler> PropertyMapper = new PropertyMapper<MauiPieChart, MauiPieChartHandler>(ViewHandler.ViewMapper)
    {
    };

    public MauiPieChartHandler() : base(PropertyMapper)
    {
    }
}

#if IOS || MACCATALYST
public partial class MauiPieChartHandler : ViewHandler<MauiPieChart, UIKit.UIView>
{
    protected override UIKit.UIView CreatePlatformView()
    {   
        var data = Foundation.NSDictionary<Foundation.NSString, Foundation.NSNumber>.FromObjectsAndKeys (
            VirtualView.Slices.Select(s => new Foundation.NSNumber(s.Count)).ToArray(),
            VirtualView.Slices.Select(s => s.Name).ToArray()
        );
        var colors = VirtualView.Slices.Select(s => s.Color.ToPlatform()).ToArray();

        var pieChart = Charts.CreatePieChartWithData(data, colors);
        return pieChart;
    }
}

#elif ANDROID
public partial class MauiPieChartHandler : ViewHandler<MauiPieChart, Android.Views.View>
{
    protected override Android.Views.View CreatePlatformView()
    {
        var data = new Java.Util.LinkedHashMap();
        var colors = new List<Java.Lang.Integer>();
        foreach (var slice in VirtualView.Slices) {
            data.Put(slice.Name, slice.Count);
            colors.Add(new Java.Lang.Integer(slice.Color.ToPlatform().ToArgb()));
        }

        var pieChart = Charts.CreatePieChart(Microsoft.Maui.ApplicationModel.Platform.CurrentActivity, data, colors);
        return pieChart;
    }
}
#endif

Now, I can access this new MauiPieChart from my user interface:

<local:MauiPieChart WidthRequest="300" HeightRequest="300">
    <local:MauiPieChart.Slices>
        <local:PieChartSlice Name="Dave's fans" Count="1" />
        <local:PieChartSlice Name="Rachel's fans" Count="5" />
        <local:PieChartSlice Name="Maddy's fans" Count="7" />
        <local:PieChartSlice Name="Beth's fans" Count="10" />
    </local:MauiPieChart.Slices>
</local:MauiPieChart>

And voila! I present to you beautiful pie charts in .NET MAUI!

Pie Chart .NET MAUI sample

What will you bind?

Thanks for following along on my journey of creating a charts binding with Native Library Interop! To check out all of the code, including the details of my API definitions and sample usage, you can find the full sample at https://github.com/rachelkang/MauiCharts. To learn more about the Native Library Interop approach, uncover the magic built in to simplify this process, and better understand when to use it, be sure to check out our documentation.

I hope that seeing my process gives you some exciting ideas for the endless possibilities that using Native Library Interop has in store for you!

Be sure to check it out yourself at CommunityToolkit/Maui.NativeLibraryInterop. I’d love to see what bindings you create, and hear about how this process goes for you!

Author

Rachel Kang (SHE/HER)
Product Manager

15 comments

Discussion is closed. Login to edit/delete existing comments.

  • Jeremy Powell · Edited

    Thanks so much for taking the time to post this! It’s great to have an article and sample to work from.

  • Otto Neff

    A nice blog post - works for just having a string in and out.
    To have NET Maui up against Flutter, ReactNative, etc. the usage of other SDKs have to be lot easier.

    Have big trouble to use 3d party libs, even with your Facebook Sample in GitHub (see Issue)

    Some research always dead ends like https://learn.microsoft.com/en-us/dotnet/android/features/maven/android-maven-library
    is just failing. No matter what I try, I never get the deps libs into the native compile.

    NET MAUI Exception:...

    Read more
  • Karthik Raja

    I am unable to build the MAUI sample due to the following error:

    “The type or namespace name ‘ChartsAndroid’ could not be found (are you missing a using directive or an assembly reference?) MauiSample (net8.0-android)”

    This error occurs when attempting to use Charts = ChartsAndroid.DotnetCharts

    • Jeremy Powell · Edited

      I also hit this issue on Mac even though dotnet build worked OK in charts/android/Charts.Android.Binding (building for Android on a Windows host worked first time)

      I’m not 100% certain that this was the problem, but after I accepted the Android SDK licenses, and cleaned the repo, it built OK on the second attempt.

      ~/Library/Android/sdk/cmdline-tools/11.0/bin/sdkmanager --licenses
      git clean -dfx
      
    • Otto Neff

      Same here… no way the Git Repro works out of the box.

      After switching to .net 9, Reference the Native Binding dll instead of Project Reference,
      it works. No debug – might the because of Framework switch.

      Version 17.11.0 Preview 7.0 / .NET MAUI 9 Preview 6

      • Rachel Kang (SHE/HER)Microsoft employee Author

        Hi Karthik and Otto - the git repo should work out of the box if you have a functional .NET MAUI working environment. Please make sure you have pulled down the latest version (as of August 2, which includes some Android bits that had not been pushed up earlier). Please also make sure you have all the prerequisites installed if you have not already: https://learn.microsoft.com/dotnet/communitytoolkit/maui/native-library-interop/get-started#prerequisites

        If that doesn't help, I recommend trying to build the .NET...

        Read more
  • Karthik Raja · Edited

    Unable to access Facebook.FacebookSdk.InitializeSDK(Microsoft.Maui.ApplicationModel.Platform.CurrentActivity, Java.Lang.Boolean.True); in facebook sample, any idea guys ?

    The facebook.Android.Binding project builds successfully but the maui sample project is not build due to the Facebook.FacebookSdk is not referred properly.

  • Julia Loyko · Edited

    It doesn’t work – this package is not available in nuget at all.

    CommunityToolkit.Maui.NativeLibraryInterop.BuildTasks

  • Dalibor Čarapić

    IMHO the article is pretty low quality:
    What are bindings? Some basic introduction would be nice. There is a link but it is at the very bottom ... once I've basically gone through the article.
    Few more words about these native charting libraries would be nice?
    The most important thing for the post is a single liner which does not provide any more information:
    | In MainPage.xaml.cs, I import ChartsMaciOS.DotnetCharts and ChartsAndroid.DotnetCharts and use...

    Read more
    • James MontemagnoMicrosoft employee

      Thanks for the feedback, worked with Rachel here on integrating some info about bindings in general and adding some more code samples.

  • Tuyến Vũ

    It looks very useful and quick.

    Quick questions:
    1/ I don’t see a mention of using Android Studio and/or Xcode to build and pack the native custom binding, is it all handled implicitly?
    2/ Will we still require a MacOS to create iOS/MacOS binding libraries?