Contributing to Uno Platform (part 3)

Xamarin Uno Platform WinUI

4 years ago

We have learned a lot about the Uno Platform in the first and second part of this series. In this article, we pick up right where we left off and continue implementing APIs of the DisplayInformation class for Android and iOS. As I mentioned last time, I will not dive too much into platform specifics and will instead focus on Uno in general. Let's dig in!

Uno Platform

When required API does not exist

The RawDpiX, RawDpiY and DiagonalSizeInInches properties are quite unusual in that they can be implemented on Android, but not on iOS. All we need on Android is the DisplayMetrics the instance we created last time using IWindowManager.

On iOS, the situation is different. No API returns physical information about the display (except for native scaling and raw screen size as we saw earlier). How do we solve such a situation? The common solution, in similar cases, would be to implement the property for Android only (by moving it into the Android-specific code file and protecting it using #if __ANDROID__ directive). Doing this is not as bad as it sounds - Uno comes with a Roslyn analyzer which automatically indicates to the app developer that an API is not implemented on the platform they are targeting. This way they know the API must be handled with care and should use platform-specific code instead. However, we must also remember, that Uno's philosophy is to match UWP behavior first - and that is very helpful here. When we check the Remarks section of RawDpiX and RawDpiY documentation, we can find the following (emphasis mine):

This property can return 0 when the monitor doesn't provide physical dimensions and when the user is in a clone or duplicate multiple-monitor setup.

This is exactly our case! iOS does not provide physical dimensions of the display and hence we can default the property value to 0. Similarly for DiagonalSizeInInches (emphasis mine):

Returns the diagonal size of the display in inches. May return null when display size information is not available or undetermined (when connected to a projector, or displays are duplicated).

Returning null is the appropriate solution here, too. In my PR I have added a <remarks> comment section to describe the reasoning behind the default values with a link using the <see> tag:

3D?

DisplayInformation has another interesting property - StereoEnabled. Docs say:

Gets a value that indicates whether the device supports stereoscopic 3D.

I know iOS devices don't support stereoscopic 3D. Android market is broad, and there are a few devices with a similar feature (see Wikipedia). Nonetheless, there is no support built into the OS, the feature is extremely niche and no clear API can tell us whether the device supports it. For now, I decided to keep things simple and return false by default:

Handling orientation change on Android

While writing the second part of this blog post series, I noticed that I missed an important detail while implementing my PR. Originally I assumed that the OrientationChanged event was already implemented for both iOS and Android, so I assumed the CurrentOrientation property works and reports orientation correctly as it changes. But I was wrong. The OrientationChanged event was implemented on iOS only and Android had set the CurrentOrientation only during the initialization of the DisplayInformation instance. Although I originally planned to implement only the properties of the DisplayInformation class, the OrientationChanged event is key and so is making sure CurrentOrientation property reports reliable values. On Android there is no event on orientation change. Instead, Android's default behavior is to destroy and re-create the Activity when the device orientation changes. Uno uses a single Activity which derives from Windows.UI.Xaml.AndroidActivity and this base class opts-out of the default behavior by handling the ConfigChanges.Orientation itself:

With the [Activity] attribute in place, Android triggers the OnConfigurationChanged method when either orientation or screen size changes.

I plugged in a call to (not yet existing) DisplayInformation.GetForCurrentView().HandleConfigurationChange() there:

We wouldn't like to have the HandleConfigurationChange method public, but luckily we don't need to. The AssemblyInfo.cs file in the Uno project contains the following:

If you are not familiar with the System.Runtime.CompilerServices.InternalsVisibleToAttribute, it allows an assembly to state that another assembly should be allowed to access its internal members. This is very useful when writing unit tests, but in this case, it is a very crucial feature as well. When we mark HandleConfigurationChange internal, it will be available in the Uno and Uno.UI projects, so it can be used when developing Uno, but it will not clutter the public interface, which is great indeed. Last missing piece of the puzzle is implementing the DisplayInformation.HandleConfigurationChange method:

First I cache the CurrentOrientation value in previousOrientation variable and then re-run the Initialize method of DisplayInformation to update all its properties, as configuration change could also be a ScreenSize change so it could affect more properties. Finally, I compare the "new" current orientation with the cached value to see if the OrientationChanged event should be invoked.

Remaining properties

The remainder of the implementation is not as interesting from the Uno point of view, so I will omit it in this blog post. If you are interested, you can check it out the full changelog of my PR on GitHub.

Creating the pull request

Now that we have everything implemented, let's create our pull request! When you push your branch to GitHub and browse the fork homepage, it will automatically suggest you to create a pull request from your branch. You can also do it using the New pull request button:

New pull request

New pull request

We first make sure to allow comparing across forks, select our fork and select Uno master branch as target:

Selecting branches for PR

Selecting branches for PR

Uno provides a default pull request description template:

  • GitHub Issue - provide the ID of the issue you were resolving. Once you start writing the number after # character you should get useful autocomplete

  • PR type - PR's primary goal, in our case Feature

  • What is the current behavior - describe behavior as is in the current master branch

  • What is the new behavior - describe behavior in the branch you are merging

  • PR Checklist - things to do before creating the pull request. Not all are applicable to all pull requests, but still serve as a nice reminder of what is expected. For example, it is easy to forget to update the Release Notes doc file, which can be found in the doc folder of the cloned solution.

    One everything is filled out properly, we can either create a Draft pull request, which signifies that the changes are not yet complete, but can be useful for work in progress for which you would like to get some feedback. If you are ready though, click the Create pull request button your PR will be ready for review!

You can create a draft PR for work in progress

You can create a draft PR for work in progress

Summary

Our pull request is out for review! We have walked through the whole process of contributing our first pull request in the Uno Platform in this three-part series. I hope you enjoyed the posts and got excited about Uno and its potential.youage you to contribute as well - you can help the project grow and you can learn plenty of new things in the process - and that's always awesome! I hope you enjoyed this series, you can expect more Uno content on this blog in the future as well. Subscribe to the newsletter, RSS or push notifications and see you next time!