Contributing to the Uno Platform (part 2)

Xamarin Uno Platform WinUI XAML

4 years ago

Welcome to the second part of the journey towards our first Uno Platform contribution. In the first part of this series we started by looking at Uno as a whole, had a first look at the codebase. This time we will look at the current implementation of DisplayInformation class, update the samples app to have some UI to test our code on, meet the UWPSyncGenerator and implement ScreenWidthInRawPixels and ScreenHeightInRawPixels.

Uno Platform

The DisplayInformation class

As I mentioned, the issue I attempt to fix has number #385 - "Add support for DisplayInformation Dimensions". First of all let's look at what exactly the UWP's DisplayInformation class is. Located in the Windows.Graphics.Display namespace, this class provides (unsurprisingly) information about the display the app is running on. In addition, the class also provides a few events, which notify about display changes like OrientationChanged. To implement the APIs, it is vital to match the UWP behavior as much as possible. Thankfully, the Microsoft Docs are very well written and each property is an adequate description which can be used as a guideline. I decided to implement all DisplayInformation properties for Android and iOS, leaving other platforms and events for a future PR. My reasoning is to keep the pull request focused and have faster feedback on the code and my approach. Once it is validated, I can continue with more confidence.

Existing Uno implementation

When I browsed into the Uno project and its Graphics/Display subfolder, I noticed DisplayInformation.cs as well as DisplayInformation.Android.cs and DisplayInformation.iOS.cs files were already present with the implementation of the AutoRotationPreferences , CurrentOrientation and OrientationChanged on iOS and AutoRotationPreferences on Android. The typical way to get an instance of DisplayInformation in UWP is via the GetForCurrentView method. On UWP this returns an instance for the current UI thread but Uno currently uses a simpler approach creating a singleton irrespectively of the calling thread:

The parameterless constructor is marked as private so new instances cannot be created. The constructor calls to a partial Initialize method.

The Initialize method is implemented in a platform-specific manner in the DisplayInformation.Android.cs and DisplayInformation.iOS.cs files. As an example, on iOS the Initialize method calls InitializeCurrentOrientation which sets up the initial CurrentOrientation value:

As you can see, StatusBar orientation is used to determine the current UI orientation, as that is the most reliable way.

Updating the sample app

To make implementation easier to test, I created a simple test user control in the SamplesApp:

The control hosts a ListView which will display the DisplayInformation property values and a Button to refresh them. The code-behind marks the UserControl with a SampleControlInfoAttribute which is used to discover and categorize the samples.

The RefreshDisplayInformation method's purpose is to retrieve the current property values from DisplayInformation and display them. My original plan was to use reflection to get the values:

That worked pretty well until I tested the code on iOS where most of the properties in the list were missing. The reason for that is the fact the linker strips out unused code to make the assembly as small as possible and the new DisplayInformation properties which were never actually accessed were removed. I fixed the code by hardcoding the access to the properties in the sample:

Granted, this lacks the simplicity and robustness of the first solution, but it works. For completeness, I also include the code of the SafeGetValue method, which uses the passed-in Func<T> to get the property value and converts it to string if possible.

Note the code handles the NotImplementedException which is the default for APIs not implemented by Uno. This is the sample running on Windows against the UWP APIs. Lots to implement!

DisplayInformation page in SamplesApp

DisplayInformation page in SamplesApp

Staying in sync with the UWP API

Now it's time for yet another part of the Uno magic. How does the team keep the API in sync with UWP? With feature updates to Windows 10 coming twice a year, it would be daunting to go through every new and changed API and ensure they are 1:1. This is where the Uno.UWPSyncGenerator project comes into the picture. First, we have to understand how are the "not implemented" APIs handled in Uno codebase. Within the Uno and Uno.UI projects you can find folders with the named Generated . In there you can find all the namespaces of the UWP API with a separate .cs file per type. For example DisplayInformation.cs is in the Generated/3.0.0.0/Windows.Graphics.Display subfolder and contains the following:

This is all auto-generated code with a NotImplementedException wrapper for everything which is not implemented yet. Also note all the classes are partial. The actual classes implementing the APIs are partial as well. The Uno.UWPSyncGenerator takes care of generating all this code and is quite smart about it as well. It uses the Microsoft.CodeAnalysis NuGet package and goes through the Uno and Uno.UI projects to check what is already declared for which platforms. For example, I add my first three DisplayInformation properties in the DisplayInformation.cs file in Uno project:

After running the UWPSyncGenerator the declared properties disappear from the Generated folder and are replaced with an informative comment:

But that's not all! What if I decided to implement a property only for Android for example?

The tool is totally able to handle this and generates:

Et voilà, the #if directive now contains a harmless false instead of __ANDROID__ symbol.

Implementing ScreenWidthInRawPixels and ScreenHeightInRawPixels

I won't dive too much into the specifics of implementing all the DisplayInformation mostly because it is platform specific code and this series of articles is devoted to the process of contributing to Uno itself. But to demonstrate the process in general, we can use the example of ScreenWidthInRawPixels and ScreenHeightInRawPixels. These properties should return the actual resolution of the display in pixels. First, I add the properties to the platform-agnostic DisplayInformation.cs file:

This will generate a conflict with the NotImplemented properties in the Generated folder, but running UWPSyncGenerator fixes this. Now let's assign these properties some useful values!

iOS

I have added a new UpdateProperties method which is called by the Initialize method in DisplayInformation.iOS.cs:

I will use the UpdateProperties method to reset the property values when the screen changes. For now, I am not implementing the events though, so I am just calling it during initialization. To make the code clear, I separate the UpdateProperties method into logical groups.

The screen size in iOS can be retrieved from the UIScreen.MainScreen.Bounds.Size property. This, however, gives us the number of layout "points" the screen has. Everything is then actually rendered with UIScreen.MainScreen.Scale scaling and scaled down to the native device resolution. This infographic is very helpful to understand the different screen "sizes". Luckily there is a UIScreen.MainScreen.NativeScale property which reports the "native scale" required to get the actual screen size:

The sample app now properly shows:

Raw screen size on iOS

Raw screen size on iOS

Android

On Android I tried to follow a similar structure to iOS:

There is a bit of additional code retrieving the display metrics and IWindowManager, as those will be reused to implement more properties later. The basic UpdateRawProperties implementation looks like this:

DisplayMetrics we retrieved above directly offer the raw screen width and height in pixels, so we just cast them and get our desired result:

Native screen size running on my OnePlus 6T

Native screen size running on my OnePlus 6T

Summary

We have checked out the existing DisplayInformation implementation in Uno, understood how the framework keeps its source codes in sync with UWP and implemented our first two properties. Next time we will briefly mention the most interesting bits from the rest of the implementation including handling of the OrientationChanged event on Android. Then we finally create our pull request a submit it for review!