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
.
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!
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:
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:
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!