Platform Detection
Runtime Detection
Detect the current platform at runtime:- System.Runtime.InteropServices
- OperatingSystem
using System.Runtime.InteropServices;
public static class PlatformHelper
{
public static bool IsWindows =>
RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public static bool IsMacOS =>
RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
public static bool IsLinux =>
RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
public static bool IsIOS =>
RuntimeInformation.IsOSPlatform(OSPlatform.Create("IOS"));
public static bool IsAndroid =>
RuntimeInformation.IsOSPlatform(OSPlatform.Create("ANDROID"));
public static bool IsBrowser =>
RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"));
}
public static class PlatformInfo
{
public static bool IsWindows => OperatingSystem.IsWindows();
public static bool IsMacOS => OperatingSystem.IsMacOS();
public static bool IsLinux => OperatingSystem.IsLinux();
public static bool IsIOS => OperatingSystem.IsIOS();
public static bool IsAndroid => OperatingSystem.IsAndroid();
public static bool IsBrowser => OperatingSystem.IsBrowser();
// Version checking
public static bool IsWindows10OrGreater =>
OperatingSystem.IsWindowsVersionAtLeast(10);
public static bool IsAndroid21OrGreater =>
OperatingSystem.IsAndroidVersionAtLeast(21);
}
Compile-Time Detection
Use conditional compilation for platform-specific code:public class PlatformService
{
public void DoSomething()
{
#if WINDOWS
// Windows-specific implementation
DoWindowsSpecific();
#elif OSX || MACCATALYST
// macOS-specific implementation
DoMacOSSpecific();
#elif LINUX
// Linux-specific implementation
DoLinuxSpecific();
#elif IOS || TVOS
// iOS-specific implementation
DoIOSSpecific();
#elif ANDROID
// Android-specific implementation
DoAndroidSpecific();
#elif BROWSER
// Browser-specific implementation
DoBrowserSpecific();
#else
// Fallback implementation
DoGenericImplementation();
#endif
}
}
Accessing Platform Services
Service Locator Pattern
Use Avalonia’s service locator to access platform services:using Avalonia;
using Avalonia.Platform;
public class PlatformServiceAccessor
{
public void AccessPlatformServices()
{
// Get platform settings
var settings = AvaloniaLocator.Current
.GetService<IPlatformSettings>();
// Get runtime platform info
var runtimePlatform = AvaloniaLocator.Current
.GetService<IRuntimePlatform>();
// Get clipboard
var clipboard = AvaloniaLocator.Current
.GetService<IClipboard>();
// Get screen information
var screens = AvaloniaLocator.Current
.GetService<IScreenImpl>();
}
}
TopLevel Services
Access services through TopLevel:using Avalonia.Controls;
using Avalonia.Platform.Storage;
public class TopLevelServiceAccessor : UserControl
{
private void AccessServices()
{
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel == null) return;
// Storage provider (file dialogs)
var storageProvider = topLevel.StorageProvider;
// Clipboard
var clipboard = topLevel.Clipboard;
// Launcher (open URLs, files)
var launcher = topLevel.Launcher;
// Input pane (virtual keyboard)
var inputPane = topLevel.InputPane;
// Insets manager (safe areas)
var insetsManager = topLevel.InsetsManager;
// System navigation (back button)
var systemNav = topLevel.SystemNavigationManager;
}
}
Windows-Specific Features
Windows Platform Handle
Access the native HWND:using Avalonia.Controls;
using Avalonia.Platform;
public class WindowsSpecificFeatures : Window
{
public IntPtr GetWindowHandle()
{
if (TryGetPlatformHandle() is PlatformHandle handle)
{
// handle.Handle is the HWND
return handle.Handle;
}
return IntPtr.Zero;
}
public void UseWin32API()
{
var hwnd = GetWindowHandle();
if (hwnd != IntPtr.Zero)
{
// Call Win32 APIs with the HWND
// Example: SetWindowLong, SendMessage, etc.
}
}
}
Windows Jump Lists
#if WINDOWS
using Windows.UI.StartScreen;
public async Task SetupJumpListAsync()
{
var jumpList = await JumpList.LoadCurrentAsync();
jumpList.Items.Clear();
var item = JumpListItem.CreateWithArguments(
"--open-recent",
"Recent Files");
item.Logo = new Uri("ms-appx:///Assets/recent.png");
jumpList.Items.Add(item);
await jumpList.SaveAsync();
}
#endif
Windows Composition
Source:src/Windows/Avalonia.Win32/Win32Platform.cs:82
macOS-Specific Features
macOS Menu Bar
Access the native macOS menu bar:using Avalonia.Controls;
public class MacOSMenuSetup
{
public void CreateMacOSMenu()
{
var menu = new NativeMenu();
// Application menu (automatically named after app)
var appMenu = new NativeMenuItem("Application");
var appSubMenu = new NativeMenu
{
new NativeMenuItem("About MyApp") { Command = AboutCommand },
new NativeMenuItemSeparator(),
new NativeMenuItem("Preferences...")
{
Command = PreferencesCommand,
Gesture = new KeyGesture(Key.OemComma, KeyModifiers.Meta)
},
new NativeMenuItemSeparator(),
new NativeMenuItem("Hide MyApp")
{
Gesture = new KeyGesture(Key.H, KeyModifiers.Meta)
},
new NativeMenuItem("Quit MyApp")
{
Gesture = new KeyGesture(Key.Q, KeyModifiers.Meta)
}
};
appMenu.Menu = appSubMenu;
menu.Add(appMenu);
// Set the menu
NativeMenu.SetMenu(Application.Current, menu);
}
}
macOS Dark Mode
using Avalonia.Platform;
public class MacOSThemeDetector
{
public void DetectTheme()
{
var settings = AvaloniaLocator.Current
.GetService<IPlatformSettings>();
if (settings != null)
{
var colorValues = settings.GetColorValues();
var isDark = colorValues.ThemeVariant == PlatformThemeVariant.Dark;
// Listen for theme changes
settings.ColorValuesChanged += (s, e) =>
{
var newIsDark = e.ThemeVariant == PlatformThemeVariant.Dark;
// Update UI for theme change
};
}
}
}
src/Avalonia.Native/NativePlatformSettings.cs:31
Linux-Specific Features
DBus Integration
#if LINUX
using Avalonia.FreeDesktop;
using Tmds.DBus;
public class LinuxDBusIntegration
{
public async Task SendNotificationAsync(string title, string body)
{
try
{
var connection = Connection.Session;
var service = connection.CreateProxy<INotifications>(
"org.freedesktop.Notifications",
"/org/freedesktop/Notifications");
await service.NotifyAsync(
"MyApp", // app_name
0, // replaces_id
"dialog-information", // app_icon
title, // summary
body, // body
new string[0], // actions
new Dictionary<string, object>(), // hints
5000 // timeout (ms)
);
}
catch (Exception ex)
{
// Handle error
}
}
}
[DBusInterface("org.freedesktop.Notifications")]
interface INotifications : IDBusObject
{
Task<uint> NotifyAsync(
string appName,
uint replacesId,
string appIcon,
string summary,
string body,
string[] actions,
IDictionary<string, object> hints,
int expireTimeout);
}
#endif
X11 Window Properties
#if LINUX
using Avalonia.X11;
public class X11WindowProperties : Window
{
public void SetX11Properties()
{
if (TryGetPlatformHandle() is PlatformHandle handle)
{
// Access X11 display and window
// handle.Handle is the X11 Window ID
}
}
}
#endif
src/Avalonia.X11/X11Window.cs:12
iOS-Specific Features
iOS View Controller Integration
#if IOS
using Avalonia.iOS;
using UIKit;
public class iOSIntegration
{
public void IntegrateWithUIKit()
{
var avaloniaView = new AvaloniaView();
avaloniaView.Content = new MyAvaloniaControl();
// Add to UIViewController
var viewController = new UIViewController();
viewController.View.AddSubview(avaloniaView);
// Layout
avaloniaView.Frame = viewController.View.Bounds;
avaloniaView.AutoresizingMask =
UIViewAutoresizing.FlexibleWidth |
UIViewAutoresizing.FlexibleHeight;
}
}
#endif
iOS Safe Areas
Handle notches and safe areas:#if IOS
using Avalonia.Platform;
using UIKit;
public class iOSSafeAreaHandler : UserControl
{
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
var insetsManager = TopLevel.GetTopLevel(this)?.InsetsManager;
if (insetsManager != null)
{
insetsManager.SafeAreaChanged += (s, args) =>
{
// Apply safe area insets
Padding = new Thickness(
args.SafeAreaPadding.Left,
args.SafeAreaPadding.Top,
args.SafeAreaPadding.Right,
args.SafeAreaPadding.Bottom
);
};
}
}
}
#endif
src/iOS/Avalonia.iOS/InsetsManager.cs:19
Android-Specific Features
Android Activity Access
#if ANDROID
using Android.App;
using Android.Content;
using Avalonia.Android;
public class AndroidIntegration
{
public void AccessAndroidAPIs()
{
var activity = AvaloniaMainActivity.Current as Activity;
if (activity != null)
{
// Use Android APIs
var context = activity.ApplicationContext;
// Example: Get package name
var packageName = context.PackageName;
// Example: Start an Android activity
var intent = new Intent(Intent.ActionView);
intent.SetData(Android.Net.Uri.Parse("https://example.com"));
activity.StartActivity(intent);
}
}
}
#endif
Android Permissions
#if ANDROID
using Android;
using Android.App;
using Android.Content.PM;
using AndroidX.Core.App;
using AndroidX.Core.Content;
public class AndroidPermissions
{
private Activity _activity;
public async Task<bool> RequestStoragePermissionAsync()
{
if (ContextCompat.CheckSelfPermission(_activity,
Manifest.Permission.ReadExternalStorage)
== Permission.Granted)
{
return true;
}
var tcs = new TaskCompletionSource<bool>();
ActivityCompat.RequestPermissions(_activity,
new[] { Manifest.Permission.ReadExternalStorage },
requestCode: 1);
// Handle result in OnRequestPermissionsResult
return await tcs.Task;
}
}
#endif
Android Back Button
#if ANDROID
using Avalonia.Platform;
public class AndroidBackHandler : UserControl
{
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
var systemNav = TopLevel.GetTopLevel(this)?.SystemNavigationManager;
if (systemNav != null)
{
systemNav.BackRequested += OnBackRequested;
}
}
private void OnBackRequested(object? sender, BackRequestedEventArgs e)
{
// Handle back button
if (CanNavigateBack())
{
NavigateBack();
e.Handled = true;
}
}
}
#endif
src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs:13
Browser-Specific Features
JavaScript Interop
#if BROWSER
using System.Runtime.InteropServices.JavaScript;
public partial class BrowserInterop
{
[JSImport("globalThis.alert")]
public static partial void Alert(string message);
[JSImport("globalThis.localStorage.setItem")]
public static partial void SetLocalStorage(string key, string value);
[JSImport("globalThis.localStorage.getItem")]
public static partial string? GetLocalStorage(string key);
[JSExport]
public static string GetDataFromCSharp()
{
return "Data from C#";
}
}
#endif
Browser APIs
#if BROWSER
using Avalonia.Browser.Interop;
public class BrowserFeatures
{
public void UseBrowserAPIs()
{
// Detect dark mode
var darkMode = DomHelper.GetDarkMode(
BrowserWindowingPlatform.GlobalThis);
// Get user agent
var userAgent = BrowserWindowingPlatform.GlobalThis
.GetProperty("navigator")
.GetProperty("userAgent")
.GetString();
}
}
#endif
src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs:41
Platform-Specific Resources
Conditional Resource Loading
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<!-- Common resources -->
<SolidColorBrush x:Key="AccentBrush" Color="Blue"/>
</Application.Resources>
<Application.Styles>
<!-- Platform-specific styles -->
<OnFormFactor Desktop="DesktopStyles.axaml"
Mobile="MobileStyles.axaml"/>
<!-- Or using platform detection -->
<StyleInclude Source="avaloniaui://avalonia/ui/fluent"
Condition="{OnPlatform Default=True, Android=False}"/>
</Application.Styles>
</Application>
OnFormFactor Extension
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<!-- Different layouts for different form factors -->
<TextBlock Text="{OnFormFactor Desktop='Desktop Mode',
Mobile='Mobile Mode'}"/>
<Image Source="{OnFormFactor Desktop='/Assets/desktop.png',
Mobile='/Assets/mobile.png'}"/>
</Grid>
</UserControl>
Platform-Specific Services Pattern
Interface Definition
public interface IPlatformService
{
Task<bool> DoSomethingAsync();
string GetPlatformInfo();
}
Platform Implementations
- Windows
- Android
- Shared
#if WINDOWS
public class WindowsPlatformService : IPlatformService
{
public async Task<bool> DoSomethingAsync()
{
// Windows-specific implementation
return true;
}
public string GetPlatformInfo()
{
return "Windows Implementation";
}
}
#endif
#if ANDROID
public class AndroidPlatformService : IPlatformService
{
public async Task<bool> DoSomethingAsync()
{
// Android-specific implementation
return true;
}
public string GetPlatformInfo()
{
return "Android Implementation";
}
}
#endif
public static class PlatformServiceFactory
{
public static IPlatformService Create()
{
#if WINDOWS
return new WindowsPlatformService();
#elif ANDROID
return new AndroidPlatformService();
#elif IOS
return new iOSPlatformService();
#else
return new DefaultPlatformService();
#endif
}
}
Usage
public class MyViewModel
{
private readonly IPlatformService _platformService;
public MyViewModel()
{
_platformService = PlatformServiceFactory.Create();
}
public async Task DoWorkAsync()
{
var result = await _platformService.DoSomethingAsync();
var info = _platformService.GetPlatformInfo();
}
}
Best Practices
1. Abstract Platform-Specific Code
Keep platform-specific code isolated behind interfaces:// Good: Platform-agnostic interface
public interface IFileService
{
Task<string> ReadFileAsync(string path);
}
// Platform-specific implementations
#if WINDOWS
public class WindowsFileService : IFileService { }
#elif ANDROID
public class AndroidFileService : IFileService { }
#endif
2. Use Dependency Injection
Register platform-specific services:public static class ServiceRegistration
{
public static void RegisterPlatformServices(IServiceCollection services)
{
#if WINDOWS
services.AddSingleton<IPlatformService, WindowsPlatformService>();
#elif ANDROID
services.AddSingleton<IPlatformService, AndroidPlatformService>();
#endif
}
}
3. Graceful Degradation
Provide fallbacks for unsupported platforms:public async Task<bool> TryUseNativeFeatureAsync()
{
#if WINDOWS
return await UseWindowsNativeFeatureAsync();
#else
// Fallback implementation
return await UseCrossplatformAlternativeAsync();
#endif
}
4. Test on All Platforms
Always test platform-specific code on actual devices or emulators.Next Steps
Desktop Platforms
Learn about desktop-specific features
Mobile Platforms
Explore mobile platform capabilities