Contributing to the Uno Platform (part 1)

Uno Platform WinUI XAML

4 years ago

As I mentioned in a previous article, I love what the Uno Platform is doing - bringing Universal Windows Platform to all platforms. I wanted to help out by contributing to the codebase on GitHub, and because I know it can be intimidating to start working on a large project like this, I would like to share with you the steps I took to get my first Pull Request to review in a series of blog posts.

Uno Platform

In this part, we cover the basics - review how Uno fits into the world of cross-platform frameworks, build Uno.UI on our local machine, familiarize ourselves with the codebase and choose an issue to work on.

Uno 101

Before we contribute, we must understand the philosophy of the Uno Platform. There is a number of cross-platform frameworks available. I will blissfully ignore JavaScript based ones and focus on C# scene. We start with Xamarin.Forms and Avalonia UI as examples and then discuss how does the Uno Platform differs from them. Xamarin.Forms has its own XAML dialect and ecosystem of custom controls and tooling. The framework uses native platform controls in the background utilizing a system of so-called renderers. This strategy unfortunately leads to the fact that the features the controls provide are usually the lowest common denominator of what all the platforms offer. Besides the fact that the XAML dialect is different in some aspects may be confusing for developers coming from the WPF and UWP ecosystem. Xamarin.Forms community is very active, however, and there are numerous libraries with custom controls, so when you need something which is not built-in yet, you can likely find it on GitHub. Avalonia UI also uses a custom XAML dialect based on WPF. In case of controls, the method is completely orthogonal to Xamarin.Forms - it opts to prefer custom rendering of controls. While this ensures the same UI everywhere, the complete lack of native feeling of the controls can be confusing to users.

The Uno Platform way

Uno Platform chooses the UWP XAML dialect not just as a starting point, but with 100% compatibility - including all excellent features like AdaptiveTriggers and control templates. Thanks to the compatibility developers can build against UWP and have access to all the tooling Visual Studio provides. It also uses native controls underneath, which ensures the user experience feels native. In contrast with Xamarin.Forms, the visual look of the controls is fully customizable with the goal of providing a pixel-perfect UI. Now, Uno does not stop there. It recognizes another painful area of building cross-platform apps - platform-specific APIs (like working with hardware). Instead of having to write platform-specific code yourself, you can use "native" Windows APIs like Geolocator, Launcher and more, which are implemented by Uno Platform and call the native platform APIs underneath. The great thing about this is the fact that once you know UWP, you can confidently write a lot of code without having to think about platform differences and resort to platform-specific code only when Uno does not yet cover the API you need to use.

Creating an Uno App

Let's create a new project using Uno. As a prerequisite, we must first install the Uno Platform Solution Templates extension via Tools - Extensions and Updates (VS 2017) or Extensions - Manage Extensions (VS 2019).

Uno Platform Solution Templates

Uno Platform Solution Templates

After we have it installed, we fire up File - New Project and choose Cross-Platform Uno App template:

Creating a new Uno App

Creating a new Uno App

The result is a set of platform projects and one shared project referenced by all of them. The platform projects contain only the bare minimum required to make the platform app run, and the application logic and UI resides in the shared project:

Shared Uno project

Shared Uno project

In the shared project you can find the App.xaml and MainPage.xaml files you would expect from a blank UWP app. Now, Shared projects are basically "packages of files" which are added to the project that references them before it is compiled. It is not a reference in the sense you would reference a DLL, and hence a Shared project cannot work on its own. The APIs it has access to are determined by the project that references it. The Android, iOS, and WASM projects all reference the Uno.UI NuGet package, which provides the Windows namespaces including controls and custom Uno implementations of many UWP APIs. The UWP project is a bit different - it does not reference Uno.UI at all. The simple reason for that is there is no need to - UWP already has all the Windows namespaces out-of-the-box. When the Shared project is referenced by UWP project, every reference to Windows namespace refers the native UWP APIs. This is exceptional because we are never limited - if there is a missing functionality in Uno, it will still work 100% in the UWP project without any issues. Besides, this means we can build apps against UWP project head first, utilizing all the latest and greatest tooling included in Visual Studio including Designer, XAML Live Edit & Continue, etc.

Building Uno.UI

Let's dig into the Uno code repository, shall we? We start by forking the Uno repository and cloning it locally. The very first step is getting Uno.UI solution to build. There are some prerequisites which you need to have installed so that Visual Studio can successfully build Uno. Luckily that has been documented recently in the Docs, so you can just follow the instructions to install everything. Build failed for me the first time, because for some reason the Uno.UI.BindingHelper.Android project failed to build and I had to rebuild it alone first and then build the whole solution again. If you run into additional problems, be sure to ask on the Uno Platform Gitter the team is very responsive and helpful!

The Samples App

Sample app projects

To test everything is working properly it is best to try running the SamplesApp. There is a project for each supported platform and one Shared Project. I suggest running Android or iOS, as those have the largest API support apart from UWP, which however uses the native UWP APIs under the hood so it is not really the best way to verify everything is up and running on your dev environment.

Sample app up and running on iPhone

Sample app up and running on iPhone

Understanding the codebase

We can start with a birds-eye look at the solution as a whole. Here is it's root folder structure:

Root of Uno.UI

Root of Uno.UI

Let's look at each folder in turn:

  • _Build - contains Uno.UI.Build project with scripts, nuspec and other resources used for building Uno

  • Addins - currently contains Uno.UI.Lottie project, which provides experimental Lottie animations support

  • SamplesApp - contains sample applications for all supported platforms (wich also serve as source for UI tests)

  • SolutionTemplate - template for File -> New Project experience you get when you install the Uno extension in Visual Studio

  • Tools - various tools and generators

  • Uno.UI - the main Uno projects, including Uno and Uno.UI projects where most of the magic happens

  • Uno.Xaml - the Uno XAML implementation, matching UWP XAML

    You may have noticed that there almost no "platform specifc" projects - there are just Uno and Uno.UI projects. How does it work then? Uno uses multi-targeting. When we open the Uno.csproj, we find the following:

The <TargetFrameworks> element lists the platforms Uno targets. There are some default references common to all frameworks and then some platform-specific ones:

In this example snippet the Condition attribute ensures that the references get included only when compiled against the MonoAndroid80 platform. So that's it for references, but what about source code? You can notice yet another interesting element in the .csproj:

This props file defines compilation constants for individual platforms:

Simpler features can be implemented using these compiler constants. Windows.System.Launcher implementation is a great example:

Based on the compilation constant, the appropriate platform-specifc code is executed. Uno.CrossTargetting.props also references another PlatformItemGroups.props file, which makes sure that for specific platforms only a subset of source code files are included for compilation based on a special suffix of the file name:

For example the files implementing the Windows.Graphics.Display.BrightnessOverrideAPI are:

  • BrightnessOverride.cs - common logic, shared by all platforms
  • BrightnessOverride.iOS.cs - iOS specific
  • BrightnessOverride.Android.cs - Android specific
  • BrightnessOverride.wasm.cs - WASM specific

Choosing an issue

There are always many active issues to choose from in the Issues section of Uno GitHub repo including bugs, feature requests and improvement suggestions. If you are not sure about the difficulty, you can always ask on Gitter and you will definitely be suggested a good first contribution issue. I got my issue suggestion from the amazing Uno architect Jérôme Laban himself, and you can find it here.

A nice isolated feature is great for first contribution

A nice isolated feature is great for the first contribution

Summary

In this part of our first Uno contribution adventure, we have done all the basics. We learned how it all fits together and chose an issue to work on. In the next article I will focus on how I approached implementing the DisplayInformation class properties for Android and iOS.