Quick start
This is the Android path, because the Android path is the template. iOS is a manual procedure against a reference shell — see Shells → iOS.
If dotnet new blazornative is not found, read Installation first —
the template is published on nuget.org, and dotnet new install BlazorNative.Templates is
the one-line install.
1. Create the app
dotnet new blazornative -n MyApp
cd MyApp
You get a .NET app (your pages, the page manifest, the NativeAOT publish head) and a runnable Android/Gradle tree with the shell already in it.
2. Publish, then gradle — always in that order
Your .NET app compiles to a native library; the Android shell loads it and renders its frames. The APK is built from the output of the publish, so the publish comes first:
# 1. the native library, for the ABI you are targeting
dotnet publish . -c Release -r linux-bionic-x64 # the emulator's ABI
dotnet publish . -c Release -r linux-bionic-arm64 # real devices
# 2. the APK
cd android
./gradlew installDebug # onto a running emulator/device
gradlew fails fast and names the exact publish command if a .so is missing. It will
not hand you an APK with stale native assets — the failure you would otherwise get is an
app that launches and shows nothing.
3. The win-x64 publish
dotnet publish . -c Release -r win-x64
That produces the loadable native BlazorNative.Runtime.dll — the same C-ABI, the same
patches, the same widget tree as on device. It exposes the exact export surface a JNA host
would bind to. Note the template ships no runnable desktop host today: this publish
gives you the loadable .dll, not an app you can launch without writing a host of your own.
Adding a page
A page is declared once — one row in AppPages.All. The runtime's mount registry and
route table are derived views of that array, so they cannot drift from it:
BlazorNativePage.Routed<BnAboutPage>("/about", "BnAboutPage"), // route + mount name
BlazorNativePage.Named<SomeScreen>("SomeScreen"), // mount name only
Adding a routed page is that one row and nothing else. Android's deep-link map
(res/raw/blazornative_routes.json, read at Intent-parse time before the native library
loads) used to be a hand-written mirror in MainActivity.kt you had to keep in sync — the last
place a page lived twice. Since v0.3.0 it is generated from AppPages.All at build time
(BlazorNative.RouteGen reads your routed rows and emits the resource), so it cannot drift from
your pages and there is no shell file to edit. Add the row; the deep link works.
Writing pages — the subset that actually renders
Only Bn* components render. There is no DOM here: <div>, <span> and <p> are not
widgets and will not appear. The BlazorNative.Analyzers package is referenced for you and
catches the common mistakes at compile time — see Analyzer rules.
Two rules worth knowing before they bite:
- Components referenced in markup must be
public. - Keep
@using Microsoft.AspNetCore.Components.Webin_Imports.razor. Without it,@bindand@onclickcompile to literal markup — no diagnostics, a green build, and a page that is silently dead on a native shell.
Two things not to delete
<TrimmerRootAssembly Include="$(AssemblyName)" />in the csproj. Without it, ILC trims your entire app: green build, zero trim warnings, and a failed mount at runtime. The line carries a comment explaining the whole failure signature.build/BionicNativeAot.targetsandglobal.json. The first is the NDK cross-compile shim — there is no bionic publish without it. The second pins the SDK feature band the ILC host relies on.
Next
- Components — what you can put on a page.
- Layout and Yoga — how things get placed, and the
one
BnScrollrule that catches everybody.