Mapbox for .NET MAUI now available

It's a long story and a huge effort to make this come true. It's the effort of more than 1 year of working, trying, stopping and resuming.

A/ The story and challenges

The story

I am the original author of Xamarin binding libraries for Mapbox SDKs as well as the Xamarin.Forms component.

However, those libraries haven't got any updates since my company, Naxam, stopped its business in 2020.

I had several requests to upgrade Mapbox SDKs binding libraries to its v10 and the Xamarin.Forms library as well in late 2020 or so because the previous versions will be deprecated and no longer receive support from Mapbox. I did try and also committed a deadline then failed to deliver because of many challenges.

The challenges

A.1/ Mapbox SDK for Android is no longer publicly available via MvnRepository

We have to generate a MAPBOX_DOWNLOADS_TOKEN and config in a way that Gradle, a build system for Android, can read and download Mapbox artifacts from the private repository.

A.2/ Mapbox SDK for iOS is no longer Objective-C compatible

Xamarin.iOS can only work with Objective-C compatible APIs, however, Mapbox SDK for iOS v10.x makes use of a lot Swift-only APIs.

A.3/ Xamarin.Forms will end of its life by 2024 and will be replaced by .NET MAUI

Xamarin.Forms is going to be deprecated and end its life by 2024, it's really a breaking change and prevent me from starting the upgrade immediately. The other thing, .NET MAUI, its successor, is totally different, I will have to rewrite the code entirely to get the best out of .NET MAUI.

B.Challenges are cleared

B.1/ Android challenge

B.1.1/ The overview

Creating a binding library for Xamarin.Android is "quite easy" if we have the native package at our hand then we simply embed it to our .NET DLL. However, it might violate the license from the native library provider, especially when the library is only available privately by downloading from its private maven repository. For a library with a public downloadable link, we can use Xamarin.Build.Download library to download the library artifacts when creating a binding library as the practice from Google Play Services for Xamarin.Android repository. This approach is great because of these advantages

  • Avoid violating the license about the distribution within the given license e.g. it isn't allowed to redistribute ourselves

  • Make the .NET DLL/nuget package very lightweight

However, it doesn't work if the native library is distributed privately via a private Maven repository.

B.1.2/ The solution

I asked myself

Is it possible to make use of Gradle, the native Android dependency management system? The final answer is YES.

I tried many attempts and failed. I tried to use MavenNET library to download libraries from Maven repository, but it is over complicated and cannot make it work as expected. I tried again with the above idea and finally created Dependencies.Gradle library which helps me create binding libraries for Xamarin.Android (.NET Android) where Android native libraries are managed by Gradle and we don't have to embed their artifacts within the .NET DLL/nuget package. It's an EUREKA for me.

<ItemGroup>
    <GradleImplementation Include="com.mapbox.maps:android:10.14.1"></GradleImplementation>
    <GradleRepository Include="https://api.mapbox.com/downloads/v2/releases/maven">
      <Repository>
      maven {
        url 'https://api.mapbox.com/downloads/v2/releases/maven'
        authentication {
          basic(BasicAuthentication)
        }
        credentials {
          // Do not change the username below.
          // This should always be `mapbox` (not your username).
          username = "mapbox"
          // Use the secret token you stored in gradle.properties as the password
          password = MAPBOX_DOWNLOADS_TOKEN
        }
      }
      </Repository>
    </GradleRepository>
  </ItemGroup>

Please check out dotnet-binding-utils repo for reference. I will try to provide more documentation for the work so others can use it.

B.2 iOS challenge

B.2.1/ The overview

Xamarin.iOS is only able to work with Objective-C compatible APIs via P-Invoke technique. However, Swift is growing from year to year and has a lot of APIs that are no longer available to Objective-C. It's a real blocker for a Xamarin developer to create a binding library for a Swift-first iOS library. Unfortunately, Mapbox v10.x is a Swift-first iOS library. At first, I gave up because the Mapbox library has huge numbers of APIs and most of them are only available to be used within Swift.

Moreover, the other part, the library is distributed privately from Mapbox CDN. We have to provide a MAPBOX_DOWNLOADS_TOKEN to download.

B.2.2/ The solution

The idea is very easy

1/ Make use of Xamarin.Build.Download

Luckily Mapbox allows to download iOS SDKs' artifacts via a link with the token in the URL, then Xamarin.Build.Download shines and helps us download the artifacts from the given link to the local machine.

<ItemGroup Condition="('$(OutputType)'!='Library' OR '$(IsAppExtension)'=='True')">
  <XamarinBuildDownload Include="$(_MapboxMapsItemsFolder)">      
    <Url>https://api.mapbox.com/downloads/v2/mobile-maps-ios/releases/ios/$(_MapboxVersion)/MapboxMaps.zip?access_token=$(_MapboxDownloadToken)</Url>
      <Kind>Zip</Kind>
  </XamarinBuildDownload>
</ItemGroup>

Our job is only to associate the local artifacts with our projects.

<ItemGroup Condition="('$(OutputType)'!='Library' OR '$(IsAppExtension)'=='True')">
  <NativeReference Include="$(_MapboxMapsSDKBaseFolder)artifacts\MapboxCoreMaps.xcframework">
    <Kind>Framework</Kind>
    <SmartLink>True</SmartLink>
    <ForceLoad>True</ForceLoad>
  </NativeReference>
</ItemGroup>

2/ Create an Objective-C wrapper library

It's unavoidable, I had to handy code an Objective-C library to act as a bridge to work with the Mapbox iOS SDK. At first, I didn't know how to do it well. I used the builder pattern to wrap Swift only classes/struct and so on. It was very time-consuming because of the big amount of SDK available APIs. After months of living with the library, I finally figured out I can

  • Make it simpler without using the builder pattern

    • For some, I changed to use the factory pattern e.g. MapInitOptionsFactory

    • I made use of extensions to the existing classes

    • I created wrapper classes where they simply hold Swift only instances and expose Objective-C compatible APIs

    • For two-way mapping of Swift only instances and Objective-C compatible instances, I have to create mapTo methods to help

  • Write JS script to generate Objective-C compatible Swift classes/enums based on the original structures

B.3 .NET MAUI challenge

It's new and hasn't many documents about writing libraries. Moreover, working with VS for Mac is a real difficulty because of performance and responsiveness. Regardless of those, I finally made the library from scratch without or very little code from the Xamarin.Forms library.

It's a huge win because I can redesign the way the code is structured, remove unnecessary code and make it the .NET MAUI way.

To validate how it works, I ported about 10 examples of the iOS native examples to demonstrate. These examples are picky and for the most complex cases when using the SDKs.

To speed up the work, I also make use of code generators to generate stuff based on Swift code.

Wrap it up

Finally, the binding libraries are now ready to use and so is the .NET MAUI library. Please check them out.

Image

We have an open issue is to build the app to the real iOS device. It's a known issue and I am trying to figure out how to resolve it.

The next step:

  • Support v10.14.x

  • Fix iOS issue

  • Port more examples to .NET MAUI and as well as .NET Android/iOS

  • Write documentations

It's a huge workload, I cannot make it happen without private sponsors. Thanks a lot to them. The repo can be found here.