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!
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:
We first make sure to allow comparing across forks, select our fork and select Uno master
branch as target:
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 autocompletePR 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!
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!