Contributing to the Uno Platform (part 2)

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:

https://gist.github.com/MartinZikmund/d4abaad0c1b6bc09083975cf352caae6

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

https://gist.github.com/MartinZikmund/a56e793249a3596849226e3685a02201

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:

https://gist.github.com/MartinZikmund/72c96a266613e5878dbc4ce054804f8e

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:

https://gist.github.com/MartinZikmund/b3168abdf621be177d1bf9aa7ed7901c

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.

https://gist.github.com/MartinZikmund/498c5b9e850cda9c8612dd44811ced68

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:

https://gist.github.com/MartinZikmund/25e13551a33a3ae389bc0835f3c928fd

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:

https://gist.github.com/MartinZikmund/960cc264882c44500e31cce9f855bba8

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.

https://gist.github.com/MartinZikmund/beed798a2c2e43f719b0774fa87cc792

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:

https://gist.github.com/MartinZikmund/f5a9191c273f721173e8756b4ba1d069

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:

https://gist.github.com/MartinZikmund/c0930f851365c541cc14e4d6beccaa7e

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

https://gist.github.com/MartinZikmund/c25d42be15d30f98b35e5229ab45a375

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

https://gist.github.com/MartinZikmund/9142bdb210fa4bbffccf8a8c27dca22b

The tool is totally able to handle this and generates:

https://gist.github.com/MartinZikmund/5436982055f725dcf0ae6414a5e7ee9b

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:

https://gist.github.com/MartinZikmund/b86ad5c7ff5e36dc435e3d4041130968

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:

https://gist.github.com/MartinZikmund/dccadaf984e60db302e56ae19ea49f5c

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.

https://gist.github.com/MartinZikmund/81ae223cc56de4a16308ff16fe6456ec

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:

https://gist.github.com/MartinZikmund/c5667b28fb4ca2180199fd93f7893c62

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:

https://gist.github.com/MartinZikmund/441b8c866eb2b641c6e6c4a98d5d9219

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:

https://gist.github.com/MartinZikmund/c3d1689540be7702368bd9c586ffd755

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!

Buy me a coffeeBuy me a coffee

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.