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
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
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.iOS.cs files were already present with the implementation of the
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 is implemented in a platform-specific manner in the
As an example, on iOS the
Initialize method calls
InitializeCurrentOrientation which sets up the initial
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.
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!
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.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/220.127.116.11/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.
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.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
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
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
These properties should return the actual resolution of the display in pixels. First, I add the properties to the platform-agnostic
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!
I have added a new
UpdateProperties method which is called by the
Initialize method in
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:
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:
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!