.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();
}
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();
}
}
// iOS project
public partial class PlatformService
{
public void ShowPlatformMessage()
{
var alert = new UIKit.UIAlertView(
"Hello from iOS!", "", null, "OK", null);
alert.Show();
}
}
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
}
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();
}
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();
}
}
// iOS project
public class iOSPlatformService : IPlatformService
{
public void ShowPlatformMessage()
{
var alert = new UIKit.UIAlertView("Hello from iOS!", "", null, "OK", null);
alert.Show();
}
}
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();
}
}
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();
}
}
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! 🚀