- Garcia's Guide to the Galaxy
- Posts
- Run a Blazor Web Application as Maui Mobile App
I took some time to investigate developing a MAUI mobile app that shares business logic with a Blazor web app by way of ViewModels and the like. What I discovered was that MAUI and XAML is a pretty cumbersome framework. My colleague Kael gave me some insight on the possibility of building a Blazor app and running that as a MAUI app. After looking into it in more detail I came to the conclusion that having a single application with multiple deployment targets is a more practical approach to cross-platform development. This article serves as a guide to developing a single application that can be deployed as both a website and an Android app. Of course there are a few platform specific things to look out for but they’re easily handled as I will demonstrate in this post.
But first, why exactly would you want to go down this route? In my mind, apps should be developed specifically for the platform they’re targeting. I figure this way they’ll be performant and consistently adhere to the platform’s own design. The reality seems to be that app designs are so wildly inconsistent anyway that there isn’t much value in this way of thinking. As for performance, although I haven’t measured it I reckon the difference for your run of the mill line of business app would be largely negligible. As the ASP.NET Blazor Hybrid documentation states:
In a Blazor Hybrid app, Razor components run natively on the device. Components render to an embedded Web View control through a local interop channel. Components don’t run in the browser, and WebAssembly isn’t involved. Razor components load and execute code quickly, and components have full access to the native capabilities of the device through the .NET platform.
Of course the real bonus of doing this approach is that there is only one UI to maintain. Perhaps you’d want to go full MAUI if the mobile app designs are significantly different from the web app design, but from my experience they generally follow almost identical patterns.
This guide pulls information from the ASP.NET Blazor Hybrid documentation, the following pages are especially helpful:
This guide assumes you have Visual Studio Code installed. You will also need:
The Razor Class Library contains the majority of the application and is referenced from the MAUI Android and Blazor web projects.
Open a terminal and navigate to the directory that will serve as the root of the solution. Run the following to create a Razor Class Library project, substituting the name if necessary:
dotnet new razorclasslib -n AppComponents
Delete some things:
This project is deployable to Android devices; essentially a MAUI app hosting Blazor components.
Using the terminal, run the following command to install the MAUI Android SDK:
dotnet workload install maui-android
maui-android
for maui
to install SDKs for all target devices. Run dotnet workload search maui
to see what else is available.Go to the root of the solution and run the following to create a MAUI Blazor Hybrid app:
dotnet new maui-blazor -n AndroidApp
Open the AndroidApp.csproj file and make the following alterations:
<TargetFrameworks>
tag<SupportedOSPlatformVersion>
and <TargetPlatformMinVersion>
and again remove the entries that do not apply to your appDelete some things:
Install Android dependencies (i.e. Android Studio and associated tools) by building the new project with the following command:
dotnet build -t:InstallAndroidDependencies -f:net8.0-android -p:AndroidSdkDirectory="<android-sdk-directory>" -p:AcceptAndroidSDKLicenses=True
$env:LOCALAPPDATA/Android/Sdk
~/.local/share/Android/Sdk
Open the VS Code command palette and run .NET MAUI: Configure Android, choosing Refresh Android environment, it will notify you of any missing components.
Ensure the Android SDK and Java SDK (OpenJDK) paths are correctly set; if not, they can be set under the .NET MAUI: Configure Android command
In my case I was missing the Android 34 platform, the Android 34 image and cmdline-tools.
Run it again and make sure it’s all tickety-boo, it should look something like a-this:
user preferred path: c:\Program Files\Microsoft\jdk-17.0.8.7-hotspot
JAVA_HOME: C:\Program Files\Microsoft\jdk-17.0.8.7-hotspot
Java SDK: shared with Visual Studio [installed]
Android service Java SDK found: C:\Program Files\Microsoft\jdk-17.0.8.7-hotspot
user preferred path: e:\AndroidSdk
MSBuild AndroidSdkDirectory: E:\AndroidSdk
ANDROID_SDK_ROOT: E:/AndroidSdk
ANDROID_HOME: E:\AndroidSdk
Android SDK: custom [installed]
Android service Android SDK found: E:\AndroidSdk
Android SDK recommended required components:
platforms/android-34 installed
build-tools/32.0.0 installed
platform-tools installed
cmdline-tools/7.0 installed
Android SDK recommended optional components:
emulator installed
system-images/android-34/google_apis/x86_64 installed
This project is a Blazor WASM app that runs in a web browser.
Using the terminal, go to the root of the solution and run the following to create a Blazor Web app:
dotnet new blazorwasm -n WebApp
Uh… there is no step 2
Use the terminal to add the projects to the solution by entering the following from the solution’s root directory:
dotnet sln add AppComponents
dotnet sln add AndroidApp
dotnet sln add WebApp
Add references to the AppComponents project to the other two projects:
dotnet add AndroidApp reference AppComponents
dotnet add WebApp reference AppComponents
Move the directory Components from the WebApp Blazor project and put it into the root of the AppComponents project.
Open the file _Imports.razor under AppComponents/Components
Remove the following line:
@using Microsoft.AspNetCore.Components.WebAssembly.Http
Change the instances of WebApp
to AppComponents
(VS Code protip: click on one of them and press Ctrl+Shift+L
to change them all at once) and save your changes
Open the file Program.cs in WebApp project
Alter the using
statement that references WebApp.Components
to be AppComponents.Components
Open the file _Imports.razor under AndroidApp
Alter the @using
statement that references AndroidApp.Components
to be AppComponents.Components
Open the file MainPage.xaml under AndroidApp
Change the line with xmlns:local
to point to the AppComponents project instead:
xmlns:components="clr-namespace:AppComponents.Components;assembly=AppComponents"
In the <RootComponent>
tag, set the ComponentType
property to {x:Type components:App}
Bring up the command palette (Ctrl+Shift+P
) and choose .NET MAUI: Configure Android
Run dotnet build
at the solution level to attempt a build.
AndroidApp.csproj
somewhere inside the first <PropertyGroup>
tag:
<AndroidSdkDirectory>path/to/androidsdk</AndroidSdkDirectory>
To run the Android app, first set the Debug Target by opening a project file such as any .cs file. Click the {} icon next to the C# icon at the bottom-right of the VS Code Window. Choose Debug Target and set up an Android emulator if you haven’t done so already.
Ensure the startup project is set to AndroidApp from the same menu.
Go to the Debug panel and click create a launch.json file. Choose .NET MAUI from the Select debugger options. This assumes that no launch.json has been set up yet. The configuration for a .NET MAUI app looks like the following:
{
"name": ".NET MAUI",
"type": "maui",
"request": "launch",
"preLaunchTask": "maui: Build"
}
Press F5 to launch the Android app in an emulator
Go to the Debug panel and click the debug target drop-down menu, selecting C#… and then choose C#: WebApp [Default configuration]
Press F5 to launch the Blazor app in a browser
Alright, now how to handle platform specific features? Gotta be some awkward switch statements or deployment directives to choose the right approach, right? Nah, Dependency Injection got you fam. I’m gonna demonstrate a common “notification” message. Now, the two methods of notification I will be employing are not strictly analogous and the implementation is no more than rudimentary, but it’s all about explaining the principle so you can build on it yourself..
Create a folder called NativeInterfaces in the AppComponents project, then create an interface with the filename INotification.cs and add the following code:
namespace AppComponents.NativeInterfaces;
public interface INotification
{
public Task ShowNotification(string message);
}
Add the Nuget package Plugin.LocalNotification to the AndroidApp project:
dotnet add AndroidApp package Plugin.LocalNotification
Create a folder called NativeImplementations in the AndroidApp project, then create a class with the filename AndroidNotification.cs and add the following code:
using AppComponents.NativeInterfaces;
using Plugin.LocalNotification;
namespace AndroidApp.NativeImplementations;
public class AndroidNotification : INotification
{
public async Task ShowNotification(string message)
{
var request = new NotificationRequest {
NotificationId = 1000,
Title = "Blazor MAUI",
Description = message
};
await LocalNotificationCenter.Current.Show(request);
}
}
Open MauiProgram.cs in the AndroidApp project
Find the line with builder.Services.AddMauiBlazorWebView();
and add the following below it:
builder.Services.AddScoped<INotification, AndroidNotification>();
Open the AndroidManifest.xml file under AndroidApps/Platforms/Android and add the following before the closing </manifest>
tag to allow the app to send notifications (documentation):
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!--Required so that the plugin can reschedule notifications upon a reboot-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Create a folder called NativeImplementations in the WebApp project, then create a class with the filename BrowserNotification.cs and add the following code:
using AppComponents.NativeInterfaces;
using Microsoft.JSInterop;
namespace WebApp.NativeImplementations;
public class BrowserNotification : INotification
{
private readonly IJSRuntime js;
public BrowserNotification(IJSRuntime jsRuntime)
{
js = jsRuntime;
}
public async Task ShowNotification(string message)
{
await js.InvokeVoidAsync("alert", message);
}
}
Open _Imports.razor in the AppComponents project and add the following line:
@using AppComponents.NativeInterfaces
Open Home.razor in the AppComponents project and inject INotification
as a service under @page "/"
:
@inject INotification NotificationService
Then add a new button under the text Welcome to your new app
:
<p>
<button onclick="@(async () => NotificationService.ShowNotification("Potatoes"))">Click me!</button>
</p>
Open Program.cs in the WebApp project and add the following line above await builder.Build().RunAsync();
:
builder.Services.AddScoped<INotification, BrowserNotification>();
Run the app on whatever platform you like and profit!
© Copyright 2024
Phil Watson
Last updated
Tuesday, 23/Jul/2024 20:07 AEST