Dynamically detecting features with API contracts (10 by 10)

News

Extraordinary Robot
Robot
Brent Rector, a principal program manager on Windows helped write this post.

The Universal Windows Platform (UWP) allows you to write your app once and target multiple device families, while also taking advantage of new APIs introduced on later versions of the OS as well as using unique APIs only present on certain device families. Apps that adapt their behavior for different devices or OS versions are called apps are called “adaptive apps”. There are three possible adaptive dimensions for most apps:

  1. Responsive user interface
  2. Version adaptive
  3. Platform adaptive

Last week we went through how responsive design can help tailor your app to the screen size. This week, we’ll focus on the remaining two adaptive dimensions.

Most adaptive apps will likely be version adaptive. For example, you may want your app to use some newer APIs that are only present on devices running different versions the UWP, while continuing to support customers who haven’t upgraded yet.

Some adaptive apps will want to be platform adaptive. Again, your app may be built for all device families, but you may want to use some mobile-specific APIs when it’s running on a mobile device. And similarly for other device families, such as IoT, HoloLens, Xbox, etc.

While this article will describe the ways in which your app can become version or platform adaptive, with Windows 10, 85% of UWP APIs are fully accessible to any app, independent of where it runs. This Universal Windows API set makes up 96.2% of the APIs used by the top 1000 apps. You can take advantage of the specialized APIs on each device to further tailor your app.



Detect features, not OS or devices


The fundamental idea behind adaptive apps is that your app checks for the functionality (or feature) it needs, and only uses it when available. The traditional way of doing this is by checking the OS version and then use those API. With Windows 10, your app can check at runtime, whether a class, method, property, event or API contract is supported by the current operating system. If so, the app can then call the appropriate API. The ApiInformation class located in the Windows.Foundation.Metadata namespace contains several static methods (like IsApiContractPresent, IsEventPresent, and IsMethodPresent) which are used to query for APIs. Here’s an example:

using Windows.Foundation.Metadata;

if(ApiInformation.IsTypePresent("Windows.Media.Playlists.Playlist"))
{
await myAwesomePlaylist.SaveAsAsync( ... );
}



This code

  • makes a runtime check for the presence of the Playlist class, then
  • statically references and calls the SaveAsAsync method on the class.

Note the ease of checking for the presence of a type on the current operating system using the IsTypePresent API. Formerly, such a check might have required LoadLibrary, GetProcAddress, QueryInterface, Reflection, use of the ‘dynamic’ keyword, and others, depending on language and framework.

Also note the static reference when making the method call. When using Reflection and/or ‘dynamic’, you lose static compile-time diagnostics that would, for example, inform you if you misspelled the method name.

Detecting with API contracts


So what’s an API contract? At heart, an API contract is a set of APIs. A hypothetical API contract could represent a set of APIs containing two classes, five interfaces, one structure, two enums and so on. We group logically related types into an API contract. In many ways, an API contract represents a feature – a set of related APIs that together deliver some particular functionality. Every Windows Runtime API from Windows 10 onward is a member of some API contract. The documentation at https://msdn.microsoft.com/en-us/library/windows/apps/dn706135.aspx describes the variety of API contracts available. You’ll see that most of them represent a set of functionally related APIs.

But the grouping into an API contract also provides you, the developer, some additional guarantees. The most important one is that when a platform implements *any* API in an API contract, we require that platform to implement *every* API in that API contract. In other words, an API contract is an atomic unit, and testing for support of that API contract is equivalent to testing that each and every API in the set is supported.

What does this mean for your app?

It allows your app to test whether the running OS supports a particular API contract and, after determining it does, call any of the APIs in that API contract without checking each one individually.

The largest and most commonly used API contract is the Windows.Foundation.UniversalApiContract. It contains nearly all of the APIs in the Universal Windows Platform. If you wanted to see if the current OS supports the UniversalApiContract, you would write the following code:

if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract"), 1, 0)
{
// All APIs in the UniversalApiContract version 1.0 are available for use
}



Right now this is a slightly silly check. All devices that run Windows 10 support version 1.0 of the UniversalApiContract. But with the next major update of Windows 10, we may introduce additional APIs, creating a version 2.0 of the UniversalApiContract and adding those new universal APIs to it. An app that wants to run on all devices, but also wants to use new APIs introduced in version 2.0 when available, could use the following code:

if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract"), 2, 0)
{
// This device supports all APIs in UniversalApiContract version 2.0
}



Of course, if your app only needed to call one single method from version 2.0, it could simply check for the method using IsMethodPresent. Use whichever approach you find the easiest.

There are other API contracts besides the UniversalApiContract. Most of them represent a feature/set of APIs not universally present on all Windows 10 platforms (else we’d add the APIs to the UniversalAPIContract). Instead, the APIs in such API contracts are present on one or more device families, but not all of them. As mentioned previously, you no longer need to check for a particular type of device, then infer the support for an API from the device family. Simply check for the set of APIs your app wants to use.

I can now rewrite my original example to check for the presence of the Windows.Media.Playlists.PlaylistsContract instead of just checking for the present of the Playlist class:


if(ApiInformation.IsApiContractPresent("Windows.Media.Playlists.PlaylistsContract"), 1, 0)
{
// Now I can use all PlayList APIs
}


Any time your app needs to call an API that isn’t present across all device families, you need to add a reference to the appropriate Extension SDK that defines the API. In Visual Studio 2015, go to the Add Reference dialog and open Extensions tabs. Today you can find the three most important extensions there: Mobile Extension, Desktop Extension and IoT Extension.

All your app needs to do, however, is check for the presence of the desired API contract and call the appropriate APIs conditionally. There’s no need to worry about the type of device.

Now the question is: I need to call the PlayList API but it’s not a universally available API. The documentation (https://msdn.microsoft.com/en-us/library/windows/apps/windows.media.playlists.playlist.aspx) tells me what API contract the class is in. But what Extension SDK(s) define(s) it?

As it turns out, the Playlist class is (currently) only available on Desktop devices, not Mobile, Xbox, and other device families. (Though that could change in a future release!) So you need to add a reference to the Desktop Extension SDK before any of the prior code compiles.

Lucian Wischik created a tool that analyzes your code and when your code calls into a platform-specific API, the analyzer verifies that you’ve done an adaptivity check around it — if you haven’t then it reports a warning. It also provides a handy “quick-fix” to insert the correct check, by pressing Ctrl+Dot or clicking on the lightbulb. See https://github.com/ljw1004/blog/blob/master/Analyzers/PlatformSpecificAnalyzer/ReadMe.md for more details. It can also be installed via NuGet if you search for it.

Let’s wrap up by looking at some more complete examples of adaptive coding for Windows 10. First, some code that IS NOT correctly adaptive.


// this code will crash if called from IoT or Mobile
async private Task CreatePlaylist()
{
StorageFolder storageFolder = KnownFolders.MusicLibrary;
StorageFile pureRockFile = await storageFolder.CreateFileAsync("myJam.mp3");
Windows.Media.Playlists.Playlist myAwesomePlaylist = new Windows.Media.Playlists.Playlist();

myAwesomePlaylist.Files.Add(pureRockFile);

// code will crash here as this is a Desktop only call.
await myAwesomePlaylist.SaveAsAsync(KnownFolders.MusicLibrary, "My Awesome Playlist", NameCollisionOption.ReplaceExisting);
}


The example below verifies that this a safe call before executing code with the API. This will prevent runtime crashes. (The PlatformSpecific analyzer will catch issues like this in your code.)


async private Task CreatePlaylist()
{
StorageFolder storageFolder = KnownFolders.MusicLibrary;
StorageFile pureRockFile = await storageFolder.CreateFileAsync("myJam.mp3");
Windows.Media.Playlists.Playlist myAwesomePlaylist = new Windows.Media.Playlists.Playlist();

myAwesomePlaylist.Files.Add(pureRockFile);

// now I'm a safe call! Cache this value if this will be queried a lot
if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Media.Playlists.Playlist"))
{
await myAwesomePlaylist.SaveAsAsync(KnownFolders.MusicLibrary, "My Awesome Playlist", NameCollisionOption.ReplaceExisting);
}
}



We are now verifying that the optional API is actually supported on this device before calling the appropriate method. Note that you’ll likely want to take this example further and never even display the UI that calls the CreatePlaylist method if your app detects that playlist functionality isn’t available on the device.

Here is another example. We’ll assume our app wants to take advantage of a Mobile device’s dedicated camera button. If I directly referenced the HardwareButtons object for the CameraPressed event while on a desktop without checking that HardwareButtons is present, my app would crash.

// Note: Cache the value instead of querying it more than once.
bool isHardwareButtonsAPIPresent =
Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons");

if (isHardwareButtonsAPIPresent)
{
Windows.Phone.UI.Input.HardwareButtons.CameraPressed +=
HardwareButtons_CameraPressed;
}



Want to learn more? Brent Rector has a great talk on API Contracts from Build 2015. And MVA has a topic on “A Developer’s Guide to Windows 10” on adaptive code that covers this topic in more detail.


Wrapping up


With week 6 of our Windows 10 by 10 development series wrapped up, we hope you try the DVLUP Quiz challenge Adaptive APIs and earn XP and points for the API contracts. Next week, we’ll continue with app to app communication talking about how you can have your app talk to another app.

For now, head on over to DVLUP for Reach us on Twitter via @WindowsDev and #Win10x10 in the meantime, telling us how you plan to use API contracts!

Quick references:


Continue reading...
 
Back
Top