Adding Platform-Specific Code to a .NET MAUI Project

Thomas McDonnell - Oct 30 - - Dev Community

.NET Multi-platform App UI (MAUI) is a powerful framework for building cross-platform applications with a single codebase. However, there are times when you need to write platform-specific code to leverage native APIs or functionalities. In this blog post, we’ll explore how to add platform-specific code to a .NET MAUI project, with examples on how to register and use this code on both Android and iOS platforms. We’ll also discuss a caveat regarding the difficulty of testing platform-specific code compared to cross-platform code.

Why Use Platform-Specific Code?

While .NET MAUI provides a unified API for most functionalities, certain features or optimizations might only be available on specific platforms. For instance, you might want to access a native API that isn’t exposed through MAUI, or you might need to implement platform-specific optimizations for performance reasons.

Adding Platform-Specific Code

To add platform-specific code in a .NET MAUI project, you can use partial classes, conditional compilation, or dependency injection. Let’s dive into each method with examples.

1. Partial Classes

Partial classes allow you to split the implementation of a class across multiple files. This is useful for separating platform-specific code.

Example:

Create a partial class in the shared project:

// Shared project
public partial class PlatformService
{
    public void ShowPlatformMessage();
}

Enter fullscreen mode Exit fullscreen mode

Implement the platform-specific code in the platform projects:

// Android project
public partial class PlatformService
{
    public void ShowPlatformMessage()
    {
        Android.Widget.Toast.MakeText(
        Android.App.Application.Context, 
        "Hello from Android!", 
        Android.Widget.ToastLength.Short).Show();
    }
}

Enter fullscreen mode Exit fullscreen mode
// iOS project
public partial class PlatformService
{
    public void ShowPlatformMessage()
    {
        var alert = new UIKit.UIAlertView(
        "Hello from iOS!", "", null, "OK", null);
        alert.Show();
    }
}

Enter fullscreen mode Exit fullscreen mode

2. Conditional Compilation

Conditional compilation allows you to include or exclude code based on the target platform. However, in my experience, using conditional compilation can make the code harder to read, especially in larger classes.

Example:

public void ShowPlatformMessage()
{
#if ANDROID
    Android.Widget.Toast.MakeText(Android.App.Application.Context, "Hello from Android!", Android.Widget.ToastLength.Short).Show();
#elif IOS
    var alert = new UIKit.UIAlertView("Hello from iOS!", "", null, "OK", null);
    alert.Show();
#endif
}

Enter fullscreen mode Exit fullscreen mode

3. Dependency Injection

Dependency injection is a design pattern that allows you to inject dependencies into a class, making it easier to manage platform-specific implementations. I often found myself using a mixture of the separate file and dependency injection approaches.

Example:

Define an interface in the shared project:

// Shared project
public interface IPlatformService
{
    void ShowPlatformMessage();
}

Enter fullscreen mode Exit fullscreen mode

Implement the interface in the platform projects:

// Android project
public class AndroidPlatformService : IPlatformService
{
    public void ShowPlatformMessage()
    {
        Android.Widget.Toast.MakeText(Android.App.Application.Context, "Hello from Android!", Android.Widget.ToastLength.Short).Show();
    }
}

Enter fullscreen mode Exit fullscreen mode
// iOS project
public class iOSPlatformService : IPlatformService
{
    public void ShowPlatformMessage()
    {
        var alert = new UIKit.UIAlertView("Hello from iOS!", "", null, "OK", null);
        alert.Show();
    }
}

Enter fullscreen mode Exit fullscreen mode

Register the platform-specific implementations in the MauiProgram.cs file:

// MauiProgram.cs
public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureServices(services =>
            {
#if ANDROID
                services.AddSingleton<IPlatformService, AndroidPlatformService>();
#elif IOS
                services.AddSingleton<IPlatformService, iOSPlatformService>();
#endif
            });

        return builder.Build();
    }
}

Enter fullscreen mode Exit fullscreen mode

Use the platform-specific service in your shared code:

// Shared project
public class MainPageViewModel
{
    private readonly IPlatformService _platformService;

    public MainPageViewModel(IPlatformService platformService)
    {
        _platformService = platformService;
    }

    public void ShowMessage()
    {
        _platformService.ShowPlatformMessage();
    }
}

Enter fullscreen mode Exit fullscreen mode

Caveat: Testing Platform-Specific Code

One important caveat to keep in mind is that platform-specific code can be more challenging to test compared to cross-platform code. This is because platform-specific code often relies on native APIs and behaviours that are not easily replicated in a test environment. To mitigate this, you can:

  • Use dependency injection to abstract platform-specific code, making it easier to mock and test.
  • Write unit tests for the shared logic and use integration tests for platform-specific code.
  • Utilise platform-specific testing tools and frameworks to test native functionalities.

Upcoming Changes in .NET MAUI 9

In .NET MAUI 9, Microsoft is reintroducing a separate project approach for platform-specific code, which feels more like the Xamarin way. This change will likely alter how you interact with your platform-specific code, making it more organised and potentially easier to manage. This approach can help in keeping platform-specific code isolated, improving readability and maintainability.

Conclusion

Adding platform-specific code to a .NET MAUI project allows you to leverage the full power of native APIs and functionalities. By using partial classes, conditional compilation, and dependency injection, you can effectively manage platform-specific implementations. However, be aware of the challenges in testing platform-specific code and plan your testing strategy accordingly. With the upcoming changes in .NET MAUI 9, the process of handling platform-specific code will evolve, offering a more structured approach.

Happy coding! 🚀

. . . . .