Leveraging Platform-Specific APIs in .NET MAUI
π Leveraging Platform-Specific APIs in .NET MAUI
A Deep Dive into Partials and Conditional Compilation
When building cross-platform applications with .NET MAUI, you quickly realize that βwrite once, run anywhereβ is more of a guiding principle than an absolute rule. Real-world apps often require:
- Native integrations (biometrics, sensors, secure storage π)
- Platform-specific UI tweaks π¨
- OS-level optimizations β‘ The question is not if you will need platform-specific codeβbut how to implement it cleanly without turning your codebase into a mess. This is where two powerful techniques come into play:
- π§© Partial Classes & Partial Methods
- βοΈ Conditional Compilation (
#if) Letβs break them down deeply, compare them, and establish best practices you can confidently apply in production-grade MAUI apps.
π§ Why Platform-Specific Code Matters
Even though .NET MAUI abstracts most functionality, some APIs are inherently platform-bound:
| Feature | Android | iOS | Windows |
|---|---|---|---|
| Biometric Auth | β | β | β οΈ Different APIs |
| File System Access | Scoped Storage | Sandbox | Full FS |
| Notifications | Firebase | APNs | Windows Push |
| Hardware Sensors | Rich APIs | Limited | Varies |
π Trying to force everything into shared code often leads to:
- β Hacks
- β Poor performance
- β Unmaintainable abstractions
π§© Approach #1: Partial Classes & Partial Methods
π Concept
Partial classes allow you to split a class across multiple filesβtypically:
- One shared file (cross-platform logic)
- Multiple platform-specific implementations
ποΈ Example Structure
Services/
βββ DeviceService.shared.cs
βββ DeviceService.android.cs
βββ DeviceService.ios.cs
βββ DeviceService.windows.cs
π§ͺ Shared Code
public partial class DeviceService
{
public partial string GetDeviceName();
}
π€ Android Implementation
public partial class DeviceService
{
public partial string GetDeviceName()
{
return Android.OS.Build.Model;
}
}
π iOS Implementation
public partial class DeviceService
{
public partial string GetDeviceName()
{
return UIKit.UIDevice.CurrentDevice.Name;
}
}
πͺ Windows Implementation
public partial class DeviceService
{
public partial string GetDeviceName()
{
return Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamily;
}
}
β Advantages
- Clean separation of concerns π§Ό
- Strongly typed and compile-safe πͺ
- Easy to scale for large apps π
- Ideal for dependency injection scenarios
β Drawbacks
- Requires multiple files π
- Slightly more setup overhead
- Can be harder to trace implementations
βοΈ Approach #2: Conditional Compilation (#if)
π Concept
Conditional compilation allows you to include/exclude code at compile time depending on the platform.
π§ͺ Example
public string GetDeviceName()
{
#if ANDROID
return Android.OS.Build.Model;
#elif IOS
return UIKit.UIDevice.CurrentDevice.Name;
#elif WINDOWS
return Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamily;
#else
return "Unknown";
#endif
}
β Advantages
- Simple and quick to implement β‘
- Everything in one place π
- Great for small differences
β Drawbacks
- Can become messy fast π§¨
- Harder to maintain in large codebases
- Violates separation of concerns
βοΈ Partials vs Conditional Compilation
π Comparative Table
| Criteria | Partial Classes π§© | #if Compilation βοΈ |
|---|---|---|
| Maintainability | βββββ | ββ |
| Scalability | βββββ | ββ |
| Readability | ββββ | ββ |
| Setup Complexity | βββ | β |
| Best for Large Projects | β | β |
| Best for Quick Fixes | β | β |
π§ When Should You Use Each?
π§© Use Partials When:
- Youβre building production-grade apps
- Logic differs significantly per platform
- You want clean architecture (MVVM, Clean Architecture)
- You plan to scale the project π Example use cases:
- Biometric authentication
- Native SDK integrations
- Secure storage implementations
βοΈ Use #if When:
- You need a small tweak
- Logic differences are minimal
- You want a quick solution π Example use cases:
- Minor UI adjustments
- Debug logging
- Small platform checks
ποΈ Hybrid Approach (Best Practice)
In real-world apps, the best solution is often a combination of both:
β Pattern
- Use partials for services and core logic
- Use
#iffor minor inline differences
π§ͺ Example Hybrid
public partial class BatteryService
{
public partial int GetBatteryLevel();
public bool IsLowBattery()
{
var level = GetBatteryLevel();
#if DEBUG
Console.WriteLine($"Battery Level: {level}");
#endif
return level < 20;
}
}
π§± Architecture Tip (PRO Level) π§
Combine partials with dependency injection:
builder.Services.AddSingleton<IDeviceService, DeviceService>();
This gives you:
- Testability π§ͺ
- Flexibility π
- Clean boundaries π§±
π Official References
For deeper understanding, check:
- https://learn.microsoft.com/dotnet/maui/platform-integration/
- https://learn.microsoft.com/dotnet/csharp/language-reference/preprocessor-directives
π Key Takeaways
- Platform-specific code is inevitable in MAUI
- Partials = clean, scalable, maintainable π§©
#if= quick, simple, but risky at scale βοΈ- The hybrid approach is often the best solution
π§© Final Thoughts
Mastering platform-specific APIs in .NET MAUI is what separates a functional app from a production-ready app. If you rely too much on conditional compilation, your codebase will eventually become fragile. On the other hand, leveraging partial classes strategically allows you to build clean, extensible, and enterprise-grade applications. π Think of it this way:
#ifis a tool- Partials are an architecture Use them accordingly, and your MAUI apps will scale without pain. β‘
