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.
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.
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
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).
After we have it installed, we fire up File – New Project and choose Cross-Platform Uno App template:
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:
In the shared project you can find the
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.
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!
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.
Understanding the codebase
We can start with a birds-eye look at the solution as a whole. Here is it’s root folder structure:
Let’s look at each folder in turn:
- _Build – contains Uno.UI.Build project with scripts,
nuspecand 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:
<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
So that’s it for references, but what about source code? You can notice yet another interesting element in the
|<Import Project="..\Uno.CrossTargetting.props" />|
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.BrightnessOverride API 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.
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.