Friday, 12 November 2021

Invoke platform code in .NET MAUI

Xamarin.Forms has the DependencyService class, which is a service locator that enables apps to invoke native platform functionality from cross-platform code. There’s a four step process for using the DependencyService to invoke native platform functionality:

  1. Create an interface for the native platform functionality, in cross-platform code.
  2. Implement the interface in the required platform projects.
  3. Register the platform implementations with the DependencyService.
  4. Resolve the platform implementations from cross-platform code, and invoke them.

For more information about the process, see Xamarin.Forms DependencyService.

While DependencyService works well, it’s not an ideal approach to invoking native code from cross-platform code in 2021 - .NET MAUI can do better! DependencyService is still present in .NET MAUI, via the compatibility layer, for ease of migration of Xamarin.Forms apps to .NET MAUI. However, there’s a better way that combines partial methods and classes with the power of multi-targeting.

In order to demonstrate this approach, I’ve created a simple sample that just returns a string that represents the platform the app is running on. This isn’t the ideal way of determining the platform the app is running on - it’s just demonstrating how to call into platform code in .NET MAUI without resorting to using DependencyService.

As always, the sample this code comes from can be found on GitHub.

Background

In a .NET MAUI app, the app project contains a Platforms folder:

Each child folder of the Platforms folder represents a platform that .NET MAUI can target, and each child folder contains platform-specific code. At build time, the compiler only includes the code from each folder when building for that specific platform. For example, when you build for Android, the files in the Platforms > Android folder will be built into the app package, but the files in the other Platform folders won’t be. This approach, known as multi-targeting can be combined with partial methods and classes to invoke native platform functionality from cross-platform code.

Implementation

In your cross-platform code, define a partial method in a partial class that represents the the operation you want to invoke on each platform. In this example, I’ve created a GetPlatform method in a MyService class:

namespace MyMauiApp
{
    public partial class MyService
    {
        public partial string GetPlatform();
    }
}

Then, in each Platform folder in the project, define the same partial class (in the same namespace) and the same partial method, but also provide the method implementation. This means you’ll have a MyService class in each of the four child folders of the Platforms folder in the solution:

Therefore, the MyService class in the Platforms > Android folder will be:

namespace MyMauiApp
{
    public partial class MyService
    {
        public partial string GetPlatform()
        {
            return "Android";
        }
    }
}

Similarly, the MyService class in the Platforms > Windows folder will be:

namespace MyMauiApp
{
    public partial class MyService
    {
        public partial string GetPlatform()
        {
            return "Windows";
        }
    }
}

Then, in your cross-platform code, create a MyService object and call its GetPlatform method to return the string that represents the platform the code is running on:

MyService service = new MyService();
label.Text = service.GetPlatform();

At build time, the compiler will (via the magic of multi-targeting) combine the cross-platform MyService class with the MyService class for the target platform and build it into the app package. No more DependencyService required!

It’s worth noting that while this approach requires you to provide implementations for all four platforms (Android, iOS, macOS, and Windows), you can also use conditional compilation to limit this to specific platforms if required (e.g. Android and iOS only).

It’s also possible to perform multi-targeting based on filenames, rather than using the Platforms folders. With this approach, the platform is included in the filename (e.g. MyService.Android.cs, MyService.Windows.cs etc.). This enables you to structure your project so that you don’t have to place all your platform code into the different Platforms folders. Maybe that’s a post for another time.

No comments:

Post a Comment