avalonia-mobile
Skill50 starsChanged 9 days agoMITView on GitHub
Use when building or fixing the Android or iOS (and Mac Catalyst) app side of an Avalonia app — project heads and startup per version (AvaloniaAndroidApplication, AvaloniaMainActivity, AvaloniaAppDelegate, IActivityApplicationLifetime vs ISingleViewApplicationLifetime), safe areas/edge-to-edge (IInsetsManager), soft keyboard (IInputPane), back button, orientation, app activation/background, haptics (PlatformFeedback), touch behavior, and mobile storage/launcher. For packaging/signing see avalonia-deployment.
In this repository23 other files it gives its agents.
- avalonia-app-developmentSkill
- avalonia-controlsSkill
- avalonia-custom-controlsSkill
- avalonia-data-bindingSkill
- avalonia-data-templatesSkill
- avalonia-deploymentSkill
- avalonia-eventsSkill
- avalonia-graphics-animationSkill
- avalonia-input-interactionSkill
- avalonia-interop-renderingSkill
- avalonia-layoutSkill
- avalonia-mvvmSkill
- avalonia-performanceSkill
- avalonia-pro-maxSkill
- avalonia-property-systemSkill
- avalonia-servicesSkill
- avalonia-stylingSkill
- avalonia-testingSkill
- avalonia-threadingSkill
- avalonia-versionsSkill
- avalonia-wpf-migrationSkill
- avalonia-xamlSkill
- avaloniaSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 0 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 145
When drawing edge-to-edge, apply SafeAreaPadding to your root layout (or rely on auto safe-area padding) so content isn't under …
- Very specific: line 179
Read at runtime: TopLevel.Screens?.Primary?.CurrentOrientation -> Avalonia.Platform.ScreenOrientation (None, Landscape, LandscapeFlipped, …
- Very specific: line 243
Content under notch/status bar: after enabling edge-to-edge — apply SafeAreaPadding (and re-apply on SafeAreaChanged).
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Avalonia Mobile (Android & iOS)
App-level concerns only. Packaging, signing, APK/IPA publishing: see avalonia-deployment. Version labels: see avalonia-versions.
Requirements
| 11.x | 12.0 / 12.1 | |
| TFM | net*-android / net*-ios (pre-net10) | net10.0-android / net10.0-ios (net10.0 required) |
| Mac Catalyst | no | (12.0+) net10.0-maccatalyst via Avalonia.iOS package |
| Packages | Avalonia.Android / Avalonia.iOS | same, version must match core Avalonia |
Standard solution layout (from avalonia-dotnet-templates xplat template): shared App project + *.Android head + *.iOS head (+ Desktop/Browser). Template defaults: Android SupportedOSPlatformVersion 23, iOS 13.0, AndroidX SplashScreen package referenced.
Android startup
(12.0+) Application class + activity
// Application.cs — initialization moved here from the activity
[Application]
public class AndroidApp : AvaloniaAndroidApplication<App>
{
public AndroidApp(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer) { }
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
=> base.CustomizeAppBuilder(builder).WithInterFont();
}
// MainActivity.cs — non-generic
[Activity(
Label = "MyApp",
Theme = "@style/MyTheme.NoActionBar",
Icon = "@drawable/icon",
MainLauncher = true,
ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
public class MainActivity : AvaloniaMainActivity { }CreateAppBuilder() / CustomizeAppBuilder() were removed from AvaloniaMainActivity — put builder customization in the AvaloniaAndroidApplication<TApp> subclass or App. (The IntPtr, JniHandleOwnership constructor is the standard .NET-Android requirement for Application subclasses; the template may omit/inline it — copy from the generated head.)
Type chain: AppCompatActivity -> AvaloniaActivity -> AvaloniaMainActivity.
(12.0+) App.axaml.cs — set the main view
public override void OnFrameworkInitializationCompleted()
{
switch (ApplicationLifetime)
{
case IClassicDesktopStyleApplicationLifetime desktop:
desktop.MainWindow = new MainWindow();
break;
case IActivityApplicationLifetime activity: // Android (12.0+)
activity.MainViewFactory = () => new MainView(); // Func<Control>
break;
case ISingleViewApplicationLifetime single: // iOS, Browser, embedded Linux
single.MainView = new MainView();
break;
}
base.OnFrameworkInitializationCompleted();
}Android no longer uses ISingleViewApplicationLifetime; keep that branch for iOS/Browser. Use a factory (a new view per activity instance), not a shared instance.
(11.x) Generic activity + single view
[Activity(Label = "MyApp", MainLauncher = true, Theme = "@style/MyTheme.NoActionBar",
ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
public class MainActivity : AvaloniaMainActivity<App>
{
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
=> base.CustomizeAppBuilder(builder).WithInterFont();
}
// App: case ISingleViewApplicationLifetime single: single.MainView = new MainView();Multiple activities (12.0+)
Android may create several instances of the main activity in one process (notification tap, return from another app), and 12.0 lets later activities use your project's Application class. IActivityApplicationLifetime.MainViewFactory (Func<Control>) is called once per new activity instance, so each gets a fresh view with its own state. Never set ISingleViewApplicationLifetime.MainView on Android 12 — the Android lifetime implements both interfaces and wraps MainView in a factory returning the same instance, so a second activity shows a blank screen. The lifetime holds only the factory; keep your own reference if you need the current view. Extra (non-main) activities subclass AvaloniaActivity (AppCompatActivity -> AvaloniaActivity) and set its Content property. AvaloniaActivity also has ActivityResult, RequestPermissionsResult (both Action<...>), event BackRequested, and OnBackPressed() / OnBackInvoked().
Rendering mode
// in AvaloniaAndroidApplication<TApp>.CustomizeAppBuilder
.With(new AndroidPlatformOptions
{
RenderingMode = new[] { AndroidRenderingMode.Egl, AndroidRenderingMode.Software }
})AndroidRenderingMode: Egl (default GPU), Vulkan, Software. First mode that initializes wins; empty list or all failing throws InvalidOperationException — keep Software as fallback.
iOS / Mac Catalyst startup
[Register("AppDelegate")]
public partial class AppDelegate : AvaloniaAppDelegate<App>
{
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
=> base.CustomizeAppBuilder(builder)
.WithInterFont()
.With(new iOSPlatformOptions
{
RenderingMode = new[] { iOSRenderingMode.Metal, iOSRenderingMode.OpenGl },
EnableHighRefreshRate = false // opt out of high refresh rate
});
}- iOS uses
ISingleViewApplicationLifetime(MainView) in all versions. - (12.0+) Scene-based lifecycle: a scene delegate is managed internally; most apps need no changes.
AvaloniaAppDelegate.Windowstaysnullafter startup — to reach theUIWindow, overrideAvaloniaView.MovedToWindow. - (12.0+) Mac Catalyst:
<TargetFrameworks>net10.0-ios;net10.0-maccatalyst</TargetFrameworks>;dotnet workload install maccatalyst,dotnet run -f net10.0-maccatalyst. - Embedding:
Avalonia.iOS.AvaloniaView(UIKit view hosting Avalonia content),Avalonia.Android.AvaloniaViewon Android.
Safe areas & edge-to-edge — IInsetsManager
var insets = TopLevel.GetTopLevel(this)?.InsetsManager; // IInsetsManager?
if (insets is not null)
{
insets.DisplayEdgeToEdgePreference = true; // draw behind system bars
insets.IsSystemBarVisible = true; // bool?
insets.SystemBarColor = Colors.Black; // Color?
Thickness pad = insets.SafeAreaPadding; // notches, bars, (keyboard excluded)
insets.SafeAreaChanged += (_, e) => Padding = e.SafeAreaPadding; // SafeAreaChangedArgs
}| Member | Notes |
DisplayEdgeToEdgePreference (bool) | Request edge-to-edge. Replaces DisplayEdgeToEdge (removed in 12) |
DisplaysEdgeToEdge | Actual current state |
SafeAreaPadding (Thickness) / SafeAreaChanged | Current safe-area insets / change event |
IsSystemBarVisible (bool?), SystemBarColor (Color?) | System bar control |
Also on TopLevel: attached SystemBarColor (TopLevel.SetSystemBarColor(control, SolidColorBrush?) / GetSystemBarColor) and TopLevel.GetAutoSafeAreaPadding / SetAutoSafeAreaPadding (whether the TopLevel auto-pads for the safe area). SystemBarTheme enum: Light (light bar background, dark foreground) / Dark.
When drawing edge-to-edge, apply SafeAreaPadding to your root layout (or rely on auto safe-area padding) so content isn't under notches/home indicator.
Soft keyboard — IInputPane
var pane = TopLevel.GetTopLevel(this)?.InputPane; // IInputPane?
pane!.StateChanged += (_, e) =>
{
// e.NewState: InputPaneState.Open / Closed
// e.EndRect: final keyboard bounds; e.StartRect (Rect?); e.AnimationDuration; e.Easing
BottomSpacer.Height = e.NewState == InputPaneState.Open ? e.EndRect.Height : 0;
};
var occluded = pane.OccludedRect; // current keyboard boundsClosedalso means "keyboard floating / overlay and not part of insets".- To request showing the keyboard:
TextInputMethodClient.ShowInputPanelwas removed in 12; the client now raisesInputPaneActivationRequested(EventHandler) and the platform decides. Don't call a show method yourself — focus aTextBox.
Back button
var top = TopLevel.GetTopLevel(this)!;
top.BackRequested += (_, e) => // EventHandler<RoutedEventArgs>, "physical Back Button pressed or back navigation requested"
{
if (Nav.CanGoBack) { Nav.GoBack(); e.Handled = true; }
};Android-specific (12.0+, per AvaloniaActivity): event BackRequested of EventHandler<AndroidBackRequestedEventArgs> (has bool Handled), plus OnBackPressed() / OnBackInvoked(). Set Handled = true to stop the default (finishing the activity); do not leave it unhandled when you consumed the back action.
Orientation
- Lock in the Android activity:
[Activity(..., ScreenOrientation = ScreenOrientation.Portrait)](Android attribute). KeepConfigurationChanges = Orientation | ScreenSize | UiModeso rotation doesn't recreate the activity. - iOS: set supported orientations in
Info.plist(UISupportedInterfaceOrientations). - Read at runtime:
TopLevel.Screens?.Primary?.CurrentOrientation->Avalonia.Platform.ScreenOrientation(None,Landscape,LandscapeFlipped,Portrait,PortraitFlipped). React to size changes via layout size changes rather than polling.
App lifecycle — IActivatableLifetime
if (Application.Current?.TryGetFeature<IActivatableLifetime>() is { } life)
{
life.Activated += (_, e) => { /* e.Kind: ActivationKind.Background / File / OpenUri / Reopen */ };
life.Deactivated += (_, e) => { if (e.Kind == ActivationKind.Background) SaveState(); };
}ActivatedEventArgs.Kindis anActivationKind:Background(enter/leave background),File(FileActivatedEventArgs.Files),OpenUri(ProtocolActivatedEventArgs.Uri),Reopen(macOS).- Replaces
IActivatableApplicationLifetime/IApplicationPlatformEvents(removed in 12). In 11.x use(ApplicationLifetime as IActivatableApplicationLifetime). - Android:
IAvaloniaActivity.Activated/Deactivatedper activity; file/URI intents arrive asFileActivatedEventArgs/ProtocolActivatedEventArgs. Register intent filters on the activity ([IntentFilter(...)]) — see Avalonia docs "Register your app to open files". - iOS: protocol/universal links are delivered as
ProtocolActivatedEventArgswithKind == ActivationKind.OpenUri. - Save state on
Deactivated(background) — mobile OSes may kill the process without further notice.
Haptics & sound — PlatformFeedback (12.1+)
// Opt a control in (default is FeedbackType.None for custom controls)
PlatformFeedback.SetFeedbackType(this, FeedbackType.Auto); // None, Auto, Sound, Haptic
this.PerformFeedback(FeedbackAction.Click); // extension on InputElement; also FeedbackAction.Hold<Button PlatformFeedback.FeedbackType="Auto" />Namespace Avalonia.Controls. Implemented on Android and iOS. FeedbackAction cannot be constructed by apps (internal constructor). Not available in 12.0/11.x.
System theme (12.1+ on Android)
12.1 detects Android system theme (dark/light) changes at runtime; earlier versions only read it at startup. Keep RequestedThemeVariant = Default (don't hard-set Light/Dark) to follow the OS, and handle ConfigChanges.UiMode on the activity. Read with TopLevel.PlatformSettings?.GetColorValues().ThemeVariant (11.x) or visual.GetPlatformSettings() (12.x).
Touch input
- (12.0+) Touch/pen select and focus on pointer release, not press;
ListBoxItem/TreeViewItemhandle their own selection. 11.x selects on press — don't write 12 code that depends on press-selection from touch. - (12.0+) Gesture events (e.g.
Pinch) are directly onInputElement, noGestures.prefix in XAML. - Hover (
:pointerover) styling is meaningless on touch; use:pressed. - Make hit targets at least ~44x44 dp (
MinHeight/MinWidth).
Storage, permissions, launcher
var top = TopLevel.GetTopLevel(this)!;
var files = await top.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { AllowMultiple = false });
await using var s = await files[0].OpenReadAsync(); // use streams, not paths
await top.Launcher.LaunchUriAsync(new Uri("https://avaloniaui.net"));- Android pickers go through the system document picker (content URIs); iOS through the document picker.
file.Path.LocalPathis unreliable — always useOpenReadAsync/OpenWriteAsync. Details:avalonia-services. - Android runtime permissions: declare in
AndroidManifest.xml(<uses-permission>), request via Android APIs from the activity;AvaloniaActivity.RequestPermissionsResult/ActivityResultcallbacks (Action<...>properties) can receive results;IActivityResultHandlerexists for this. - iOS: add usage descriptions (
NSCameraUsageDescription, etc.) toInfo.plist.
Page-based navigation (12.0+)
12.0 adds Page, ContentPage, DrawerPage, CarouselPage and related page types for mobile-style navigation (page lifecycle events fire after transitions complete). Not in 11.x — there use a ContentControl + ViewModel navigation. See avalonia-controls / avalonia-mvvm and the Avalonia docs for the page API; the dotnet template can scaffold a page-hosted main view. Wire TopLevel.BackRequested to the page stack.
Common Mistakes
- Android 12.x with
AvaloniaMainActivity<TApp>orISingleViewApplicationLifetime— use non-genericAvaloniaMainActivity+[Application] AvaloniaAndroidApplication<TApp>+IActivityApplicationLifetime.MainViewFactory. - Overriding
CustomizeAppBuilderon the activity (12.x) — removed; put it on the Application class. - Missing
[Application]attribute on theAvaloniaAndroidApplication<TApp>subclass — app never initializes. - Wrong TFM — 12.x mobile heads need
net10.0-android/net10.0-ios. DisplayEdgeToEdge/ShowInputPanel/IActivatableApplicationLifetime— removed in 12; useDisplayEdgeToEdgePreference,InputPaneActivationRequested,TryGetFeature<IActivatableLifetime>().- Content under notch/status bar after enabling edge-to-edge — apply
SafeAreaPadding(and re-apply onSafeAreaChanged). - Keyboard covers inputs — listen to
InputPane.StateChanged, pad/scroll byEndRect.Height. - Accessing
TopLevel.InsetsManager/InputPanetoo early — null before attachment; useLoaded/OnAttachedToVisualTree. - Forgetting
e.Handled = trueon back handling — app exits instead of navigating back. - Using
PlatformFeedbackon 12.0 — 12.1+ only; custom controls default toFeedbackType.None. - Assuming selection on press for touch — 12.x selects on release.
- Using file paths from pickers on mobile — use stream APIs.
- Removing
ConfigChanges.UiMode/Orientation/ScreenSize— activity restarts on rotate/theme change and view state is lost. - Reading
AvaloniaAppDelegate.Windowon iOS 12 — null; scene delegate owns the window.
Sources
- Android: https://docs.avaloniaui.net/docs/platform-specific-guides/android ; iOS: https://docs.avaloniaui.net/docs/platform-specific-guides/ios
- Breaking changes: https://docs.avaloniaui.net/docs/avalonia12-breaking-changes
- API: https://docs.avaloniaui.net/api/avalonia/android , /api/avalonia/controls/platform , /api/avalonia/controls/applicationlifetimes
- Templates: https://github.com/AvaloniaUI/avalonia-dotnet-templates (templates/csharp/xplat)
- PlatformFeedback: https://github.com/AvaloniaUI/Avalonia/pull/21189
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

