Practical .NET
Write Once, Run Everywhere with .NET and the Uno Platform
Right now, in Visual Studio, you can create a solution that takes a single UI with its code and shares it across Windows, Android, macOS, iOS and web browsers. It's not a perfect cross-platform solution (yet), but it's here now.
I don't usually step outside the default Microsoft technologies. However, the open source Uno platform delivers right now what Microsoft's MAUI platform promises for November 2022: The ability to write C# code using .NET and have run it on Windows, Android, macOS and iOS devices.
But Uno also offers right now more than MAUI currently promises for the future: Thanks to WebAssembly, your Uno Platform application will also run in web browsers (any web browser). I like this technology, at least in part because it seems to me to be the next obvious step for .NET development (as I've discussed elsewhere).
Both MAUI and the Uno Platform are extensions of Microsoft's Xamarin toolset that allows you write applications that run on multiple platforms. What the Uno Platform does (and MAUI currently doesn't include) is take the open source technology that Microsoft sponsors in this area (Xamarin, Mono, etc.) and let you include client-side solutions for your browser. In this article, I'll show you how to create a Uno Platform project and discuss what I see as the one significant limitation with Uno.
If you want, you can skip my article and go straight to the source because I'm basically pulling together material from Uno documentation here and here, enhanced with some of my own experience with Uno projects.
Configuring Visual Studio
You don't need to do much to get Visual Studio ready to create a cross-platform app with Uno. First, you need a compatible version of Visual Studio (I used Visual Studio 2019 Community Edition but any version from 16.3 on is supposed to work). You'll also need to make sure that Visual Studio has these three workloads installed:
- Universal Windows Platform
- Mobile development with .NET (Xamarin)
- ASP.NET and web
Odds are you don't have the Xamarin workload included so you'll need to shut down Visual Studio, run the Visual Studio installer and add the Xamarin workload.
Next, you'll need the Uno Platform project templates. To get them, from Visual Studio's Extensions menu, select Manage Extensions. In the resulting dialog box make sure that, on the left, you have Online | Visual Studio Marketplace selected and then, in the search box on the right, search for "Uno." Once you've found the Uno Platform Solution Templates option, select it and click the Download button. As with any extension, that only schedules the templates to be installed so you'll need to close Visual Studio, wait for the installation dialog box to appear, click the Modify button and wait for the installation to finish.
Before starting Visual Studio, you can pre-emptively fix a potential problem. When you finally get to compile an Uno platform application you may get an error message that says "Platform environment variable set to mcd." You can wait to see if you get the error or check for/fix the problem now. The steps to eliminate the problem are:
- Click the Start button and type "My Compute" (depending on what version of Windows you have, the resulting list will include either "My Compute" or "This PC")
- Right-click on This PC/My Computer and select Properties
- Select either Advanced or Advanced system settings (again, depending on what version of Windows you have)
- Click the Environment Variables button
- In the list of variables, find the Platform environment
- If the variable both exists and is set to mcd, click the Delete button to remove the variable
If you wait to see if you get the problem when you finally do compile your project, you'll need to restart Visual Studio after deleting the variable.
Creating a Uno Solution
You're now ready to create a Uno solution. After starting Visual Studio, from the File menu, select New | Project and, in the Create a new project dialog, pick the Cross-Platform App (Uno Platform) template (as a new template, it's probably right at the top of the list). Right now there are two templates -- make sure you pick the template whose description says "create a cross-platform XAML app" (the other template is for creating a Uno class library project).
Work through the Wizard, giving your project a name. You may need to allow access through the Windows Firewall during this process and you'll definitely need to accept the Android SDK Preview License. When you're done, you'll have a solution with separate projects for Android, macOS, iOS, Windows and the browser, plus a shared project that contains the files used by all of those projects. That shared project is where you'll put the Xamarin.Forms that make up your application's UI, along with any code that runs on all of the platforms (which is, potentially, all of the code for your application).
It's pretty easy to figure out which projects are targeting which platforms: The Android project is called <yourProjectName>.Droid, the browser version is <yourProjectName>.Wasm, the Windows project is <yourProjectName>.UWP, the shared project is <yourProjectName>.Shared and so on. This is similar to the way that Xamarin organizes its multi-platform solutions (and one of the things that changes in MAUI where the various target platforms become just platform options within a single project).
If this is your first Uno/Xamarin solution, you may want to remove the macOS and iOS projects which, because of various licensing restrictions, have to be compiled on a MacIntosh computer (Xamarin projects have extensive support for shooting builds over to a MacIntosh computer on a network and monitoring those builds ... but you do need a MacIntosh computer). There's no harm in leaving the projects in your solution but, with the Uno Platform much of your configuration has to be done on a project-by-project basis, so fewer projects equals less configuration work.
For example, to finish configuring your solution, you need to right-click on each project, select "Manage NuGet packages for Solution ... " and do these two things:
- In the Updates tab of the resulting dialog, update the Uno packages (Don't update anything else! If you update the Console packages, your solution won't compile)
- In the Browse tab, find the NuGet package Refactored.MvvmHelpers and install it
In the in the <yourProjectName>.Shared project project, to ensure that you get all the IntelliSense support you're entitled to when working with your UI forms, open the MainPage.xaml form and make sure that the drop-down list in the upper-left hand corner of your editor form is set to <yourProjectName>.UWP. Then make the UWP project your startup project and build your solution.
That's all that's required from a "universal" point of view. However, for the individual projects, you have a little more work to do.
If you're planning on testing your Android project, you'll need to add these NuGet packages to your Droid Project: Xamarin.AndroidX.Lifecycle.LiveData, Xamarin.AndroidX.Browser and Xamarin.Google.Android.Material. You'll also need to ensure that you have the Android emulators ... but the easiest way to check if you do is to compile and run your solution with the <yourProjectName>.Droid project selected as your startup project. If you don't have them, I've discussed how to set them up.
If you want to test your application in a browser then, in the *.Wasm project's csproj file, comment out this line:
<EmbeddedResource Include="WasmCSS\Fonts.css" />
After you've made all of those configuration changes, close the solution and re-open it. You're now ready to create your cross-platform application.
Creating Shared Components
By default, any files that you add to the Shared project are automatically copied into the other projects and used in those projects. The Shared project is, therefore, where you'd prefer to make most of your changes.
(Editor's note: Substantial parts of the following text have been added/revised from the original. See reader comments -- and thanks, "Shimmy.")
But this leads to the one of the major differences between the Uno platform and Xamarin (and, I assume, MAUI): If you want to extend the Shared project with third party packages ... well you can't. Shared Projects don't support adding either NuGet packages or references to other libraries/projects. Instead, the files in the Shared project are just copied into each of the platform-specific projects at compile time. So, as far as shared components go, you're currently limited to the Xamarin Form controls available in the Shared project and any code you'd care to write or copy into the Shared project.
What you can do is extend the other projects in the Solution with NuGet packages and class library references (you saw this in action earlier when you added the Refactored.MvvmHelpers package to all the projects). Any package/reference you add to all the projects can be used in the Shared project.
If, however, you have a package/reference that you add to only some of your platform-specific projects then you have at least two strategies open to you.
One option is to put the code that uses the NuGet package/library reference in the Shared project. You'll need to surround that code with preprocessor directives so that, when the code is copied into a project without the package/reference, it isn't compiled. Essentially, this is the same option you use when targeting code for specific frameworks.
This option makes sense when most of your platform-specific projects have the package/reference. This code (taken from the Uno site) in the Shared project picks the right using statement for the platform for Android, iOS, or Windows:
#if __ANDROID__
using _View = Android.Views.View;
#elif __IOS__
using _View = UIKit.UIView;
#else
using _View = Windows.UI.Xaml.UIElement;
#endif
Alternatively, you can put the code that depends on the package/reference in the platform-specific projects that actually have the package/reference. This is the strategy that makes sense when there's only one project that has the package/reference or the code that leverages the package/reference is different in each project.
Building Your First Form
Having said that, the Xamarin.Forms components that you can use in the Shared project do provide quite a lot of UI-related functionality. The Shared project holds the startup page for your project (MainPage.xaml file) and you can start experimenting with Uno using that page. Here's a simple form with a label, a button and a textbox (I also changed the default Grid in the MainPage to a Page). The button has its Click event tied to a method called SayHello:
<Page
x:Class="UnoTelerik.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel>
<TextBlock Name="HelloLabel"/>
<TextBox x:Name="FirstName"></TextBox>
<Button Click="Button_Click">Click Me!</Button>
</StackPanel>
</Page>
The code-behind file for the form can contain any code you want to add. I've put in a SayHello method that updates the UI label with a message based on the value entered in the textbox:
private void SayHello(object sender, RoutedEventArgs e)
{
HelloLabel.Text = "Hello, " + FirstName.Text;
}
In a production app, of course, I'd implement the MVVM model, which Uno supports (in fact, here's a list of tools and technologies that the Uno platform supports).
And here's the payoff: If you use Visual Studio's option to set multiple startup projects, you can now press F5 and get a display like the one in Figure 1 that shows my form running on Android, Windows and in the browser all at once. And that, you have to admit, is pretty cool.
Further reading:
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.