Goal
AvaloniaActivity, AvaloniaApplication, and AvaloniaAppDelegate lifetimes so your shared code boots correctly on each platform.Why this matters
Prerequisites
Install .NET workloads and mobile SDKs:
# Android
sudo dotnet workload install android
# iOS (macOS only)
sudo dotnet workload install ios
# Optional: wasm-tools for browser
sudo dotnet workload install wasm-tools
Check workloads with dotnet workload list.
Project structure:
MyApp): Avalonia cross-platform code.dotnet new avalonia.app --multiplatform creates the shared project plus heads (MyApp.Android, MyApp.iOS, optional MyApp.Browser). The Android head references Avalonia.Android (which contains AvaloniaActivity and AvaloniaApplication); the iOS head references Avalonia.iOS (which contains AvaloniaAppDelegate).
Keep trimming/linker settings in Directory.Build.props so shared code doesn't lose reflection-heavy ViewModels. Example additions:
<PropertyGroup>
<TrimMode>partial</TrimMode>
<IlcInvariantGlobalization>true</IlcInvariantGlobalization>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
Use TrimmerRootAssembly or DynamicDependency attributes if you depend on reflection-heavy frameworks (e.g., ReactiveUI). Test Release builds on devices early to catch linker issues.
ISingleViewApplicationLifetime hosts one root view. Configure in App.OnFrameworkInitializationCompleted (Chapter 4 showed desktop branch).
public override void OnFrameworkInitializationCompleted()
{
var services = ConfigureServices();
if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
{
singleView.MainView = services.GetRequiredService<ShellView>();
}
else if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = services.GetRequiredService<MainWindow>();
}
base.OnFrameworkInitializationCompleted();
}
ShellView is a UserControl with mobile-friendly layout and navigation.
Hot reload: on Android, Rider/Visual Studio can use .NET Hot Reload against MyApp.Android. For XAML hot reload in Previewer, add <ItemGroup><XamlIlAssemblyInfo>true</XamlIlAssemblyInfo></ItemGroup> to the shared project and keep the head running via dotnet build -t:Run.
Use view-model-first navigation (Chapter 12) but ensure a visible Back control.
<UserControl xmlns="https://github.com/avaloniaui" x:Class="MyApp.Views.ShellView">
<Grid RowDefinitions="Auto,*">
<StackPanel Orientation="Horizontal" Spacing="8" Margin="16">
<Button Content="Back"
Command="{Binding BackCommand}"
IsVisible="{Binding CanGoBack}"/>
<TextBlock Text="{Binding Title}" FontSize="20" VerticalAlignment="Center"/>
</StackPanel>
<TransitioningContentControl Grid.Row="1" Content="{Binding Current}"/>
</Grid>
</UserControl>
ShellViewModel keeps a stack of view models and implements BackCommand/NavigateTo. Hook Android back button (Next section) to BackCommand and mirror the same logic inside AvaloniaAppDelegate to react to swipe-back gestures on iOS.
Phones have notches and OS-controlled bars. Use IInsetsManager to apply safe-area padding.
public partial class ShellView : UserControl
{
public ShellView()
{
InitializeComponent();
this.AttachedToVisualTree += (_, __) =>
{
var top = TopLevel.GetTopLevel(this);
var insets = top?.InsetsManager;
if (insets is null) return;
void ApplyInsets()
{
RootPanel.Padding = new Thickness(
insets.SafeAreaPadding.Left,
insets.SafeAreaPadding.Top,
insets.SafeAreaPadding.Right,
insets.SafeAreaPadding.Bottom);
}
ApplyInsets();
insets.Changed += (_, __) => ApplyInsets();
};
}
}
Soft keyboard (IME) adjustments: subscribe to TopLevel.InputPane.Showing/Hiding and adjust margins above keyboard.
var pane = top?.InputPane;
if (pane is not null)
{
pane.Showing += (_, args) => RootPanel.Margin = new Thickness(0, 0, 0, args.OccludedRect.Height);
pane.Hiding += (_, __) => RootPanel.Margin = new Thickness(0);
}
Touch input specifics: prefer gesture recognizers (Tapped, DoubleTapped, PointerGestureRecognizer) over mouse events, and test with real hardware—emulators may not surface haptics or multi-touch.
MainActivity.cs inherits from AvaloniaActivity. Override AppBuilder CustomizeAppBuilder(AppBuilder builder) to inject logging or DI.MyApplication.cs can inherit from AvaloniaApplication to bootstrap services before the activity creates the view.AndroidManifest.xml: declare permissions (INTERNET, READ_EXTERNAL_STORAGE), orientation, minimum SDK.Resources/mipmap-*, Resources/xml/splashscreen.xml for Android 12+ splash.<AndroidEnableProfiler>true</AndroidEnableProfiler> in Debug configuration.OnBackPressed or using AvaloniaLocator.Current.GetService<IMobileNavigation>().public override void OnBackPressed()
{
if (!AvaloniaApp.Current?.TryGoBack() ?? true)
base.OnBackPressed();
}
TryGoBack calls into shared navigation service and returns true if you consumed the event. To embed Avalonia inside an existing native activity, host AvaloniaView inside a layout and call AvaloniaView.Initialize(this, AppBuilder.Configure<App>()...).
AppDelegate.cs inherits from AvaloniaAppDelegate. Override CustomizeAppBuilder to inject services or register platform-specific singletons.Program.cs wraps UIApplication.Main(args, null, typeof(AppDelegate)) so the delegate boots Avalonia.Info.plist: permissions (e.g., camera), orientation, status bar style.LaunchScreen.storyboard or SwiftUI resources in Xcode.AvaloniaViewController to embed Avalonia content inside UIKit navigation stacks or tab controllers.Handle universal links or background tasks by bridging to shared services in AppDelegate. For swipe-back gestures, implement TryGoBack inside AvaloniaNavigationController or intercept UINavigationControllerDelegate callbacks.
Inject platform implementations for IClipboard, IStorageProvider, notifications, and share targets via dependency injection. Register them in AvaloniaLocator.CurrentMutable inside CustomizeAppBuilder to keep shared code unaware of head-specific services.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> and use runtime requests.NSPhotoLibraryUsageDescription).AppBundle) instead of relying on arbitrary file system access.EssentialsPermissions helper libraries carefully—Release builds with trimming must preserve permission classes. Validate by running dotnet publish -c Release on device/emulator.IPushNotificationService) that platform heads implement and inject into shared locator.Tapped/DoubleTapped events for simple gestures; PointerGestureRecognizer for advanced ones.TopLevel.Screen to detect orientation/size classes and expose them via your view models.Task.Run, consider battery impact; use async I/O where possible.adb shell dumpsys gfxinfo or Xcode's Metal throughput counters to detect rendering bottlenecks.cd MyApp.Android
# Debug build to device
msbuild /t:Run /p:Configuration=Debug
# Release APK/AAB
msbuild /t:Publish /p:Configuration=Release /p:AndroidPackageFormat=aab
Sign with keystore for app store.
dotnet build -t:Run -f net8.0-ios works on macOS with Xcode installed.--warnaserror on linker warnings to catch missing assemblies early.Avalonia's Tizen backend (Avalonia.Tizen) targets smart TVs/wearables. The structure mirrors Android/iOS: implement a Tizen Program.cs that calls AppBuilder.Configure<App>().UseTizen<TizenApplication>() and handles platform storage/permissions via Tizen APIs.
Mobile code often reuses single-view logic for WebAssembly. Check ApplicationLifetime for BrowserSingleViewLifetime and swap to a ShellView. Storage/clipboard behave like Chapter 16 with browser limitations.
ShellView.StorageProvider and test on device (consider permission prompts).AvaloniaView on Android, AvaloniaViewController on iOS) and pass data between native and Avalonia layers.AvaloniaActivity, AvaloniaApplicationAvaloniaAppDelegate, AvaloniaViewControllerSingleViewApplicationLifetime.csIInsetsManager, IInputPaneAvaloniaLocator, IClipboardAvalonia.Tizensamples/ControlCatalog.Android, samples/ControlCatalog.iOSWhat's next