Essential practices

After you learn the basics of configuring the development environment and building apps utilizing the SensorCore SDK, there are still a few more things a developer needs to know. There are some general patterns and practices about how one should use SensorCore to ensure stability and compatibility. Please note that the same essential patterns and practices are used in the samples in an identical way, although they are only documented in detail in here. In this section, we will go through initializing, error handling, and application lifecycle management.

Initializing SensorCore

To make it easier to switch between the actual hardware class and the simulator, we use an alias for the class, and refer to it with the Monitor keyword throughout the sample. Here is a sample from Places:

using Monitor = Lumia.Sense.PlaceMonitor;

Verifying hardware support

It is important to understand that the SensorCore SDK relies on hardware which is not present on all phones. For that reason, the first thing you should do is to check if the device supports the feature of the SensorCore SDK you are using. Lumia devices previous to Windows Phone 8.1 and Cyan SW update do not support SensorCore.

You can check if a sensor is supported by the device by using the IsSupportedAsync method.

if( !await StepCounter.IsSupportedAsync() )
{
    // Show error message or hide UI controls...
}

To check for specific feature availability, you can use the SenseHelper.GetSupportedApiSetAsync. API documentation, which specifies the API set that is required for each feature. For example, querying place history in Place Monitor requires API set 4 or greater. Details about the API sets are available in the application migration chapter.

uint apiSet = await SenseHelper.GetSupportedApiSetAsync();

if( apiSet >= 4 )
{
    // New Place monitor APIs are supported by this device
    var history = await _placeMonitor.GetPlaceHistoryAsync(
    DateTime.Now.Date, DateTime.Now - DateTime.Now.Date );
}

In earlier versions of the SensorCore SDK, SenseHelper.GetSupportedCapabilitiesAsync was used to check for feature availability. That API has now been deprecated and is being replaced by SenseHelper.GetSupportedApiSetAsync. This change was done to make it easier to check for feature group availability instead of having to check each feature one by one.

How apps should deal with motion data settings

The separation of non-location based and location based sensors in motion data 2.X has resulted in a bit of extra work for applications to properly support different versions of SensorCore. In this section, we will be covering different ways of managing backwards compatibility.

Dn932087.essential-practices(en-us,WIN.10).jpg Dn932087.quickstart-5(en-us,WIN.10).jpg

Motion data settings version 1 on the left, motion data settings version 2 on the right

For non-location based (Activity monitor, Step counter) sensors, motion data 2.X represents quite a big change: system location and motion data settings no longer need to be enabled for the sensors to work. However, to also support devices running legacy motion data 1.X, you should implement a mechanism to instruct users to enable required settings. Another option would be to instruct users to update motion data settings to the latest version if you don't want to support legacy motion data settings.

// Starting from version 2 of motion data settings, Step counter
// and Acitivity monitor are always available. In earlier versions,
// system location setting and motion data had to be enabled.

MotionDataSettings settings = await SenseHelper.GetSettingsAsync();

if( settings.Version < 2 )
{
    if( !settings.LocationEnabled )
    {
        MessageDialog dlg = new MessageDialog( "In order to count steps you need
            to enable location in system settings. Do you want to open settings
            now?", "Information" );
    
        dlg.Commands.Add( new UICommand( "Yes", new UICommandInvokedHandler(
            async ( cmd ) => await SenseHelper.LaunchLocationSettingsAsync() ) ) );
        dlg.Commands.Add( new UICommand( "No" ) );
        await dlg.ShowAsync();
    }
        else if( !settings.PlacesVisited )
        {
            MessageDialog dlg = new MessageDialog( "In order to count steps you need
            to enable motion data collection in motion data settings. Do you want to
                open settings now?", "Information" );
            
            dlg.Commands.Add( new UICommand( "Yes", new UICommandInvokedHandler(
            async ( cmd ) => await SenseHelper.LaunchSenseSettingsAsync() ) ) );
            
            dlg.Commands.Add( new UICommand( "No" ) );
            await dlg.ShowAsync();
        }
    }
}

Please note that if your application depends on biking activity class, you need to make sure that system location is enabled, and motion data settings detailed data and Places visited are enabled. For more accurate and detailed MovingInVehicle data, you need to use these same settings.

uint apiSet = await SenseHelper.GetSupportedApiSetAsync();

if( apiSet < 3 )
{
    MessageDialog dlg = new MessageDialog( "Unfortunately this device does
            not support biking detection" );
    await dlg.ShowAsync();
}
else
{
    MotionDataSettings settings = await SenseHelper.GetSettingsAsync();
        
    if( !settings.LocationEnabled )
    {
        MessageDialog dlg = new MessageDialog( "In order to recognize biking you
            need to enable location in system settings. Do you want to open settings
            now?", "Information" );
    
        dlg.Commands.Add( new UICommand( "Yes", new UICommandInvokedHandler(
            async ( cmd ) => await SenseHelper.LaunchLocationSettingsAsync() ) ) );
    
        dlg.Commands.Add( new UICommand( "No" ) );
        await dlg.ShowAsync();
    }
    else if( settings.DataQuality == DataCollectionQuality.Basic )
    {
        MessageDialog dlg = new MessageDialog( "In order to recognize biking you
            need to enable detailed data collection in motion data settings. Do you
            want to open settings now?", "Information" );
    
        dlg.Commands.Add( new UICommand( "Yes", new UICommandInvokedHandler(
        async ( cmd ) => await SenseHelper.LaunchSenseSettingsAsync() ) ) );
    
        dlg.Commands.Add( new UICommand( "No" ) );
        await dlg.ShowAsync();
    }
}

For location based sensors (Place monitor, TrackPoint monitor), things haven't changed that much in Motion data 2.X. To access the sensors, the user needs to enable Places visited. Previously, the user had to enable motion data. If your application needs more accurate TrackPoint data, you will have to instruct the user to enable detailed data collection. Also, system location settings must be set to on in order to enable detailed data collection.

uint apiSet = await SenseHelper.GetSupportedApiSetAsync();
MotionDataSettings settings = await SenseHelper.GetSettingsAsync();

if( !settings.LocationEnabled )
{
    MessageDialog dlg = new MessageDialog( "In order to see your running and
        walking routes, you need to enable location in system settings. Do you
        want to open settings now?", "Information" );
    
    dlg.Commands.Add( new UICommand( "Yes", new UICommandInvokedHandler(
    async ( cmd ) => await SenseHelper.LaunchLocationSettingsAsync() ) ) );
    
    dlg.Commands.Add( new UICommand( "No" ) );
    await dlg.ShowAsync();
}
else if( !settings.PlacesVisited )
{
    MessageDialog dlg = null;

    if( settings.Version < 2 )
    {
        dlg = new MessageDialog( "In order to see your running and walking
            routes, you need to enable motion data collection in motion data
            settings. Do you want to open settings now?", "Information" );
    }
    else
    {
        dlg = new MessageDialog( "In order to see your running and walking
            routes, you need to enable Places visited in motion data settings. Do you
            want to open settings now?", "Information" );
    }
    
    dlg.Commands.Add( new UICommand( "Yes", new UICommandInvokedHandler(
        async ( cmd ) => await SenseHelper.LaunchSenseSettingsAsync() ) ) );
    dlg.Commands.Add( new UICommand( "No" ) );
    await dlg.ShowAsync();
}
else if( apiSet >= 3 && settings.DataQuality == DataCollectionQuality.Basic )
{
    MessageDialog dlg = new MessageDialog( "In order to get better quality
        running and walking routes you should enable detailed data collection in
        motion data settings. Do you want to open settings now?", "Helpful tip");
    
    dlg.Commands.Add( new UICommand( "Yes", new UICommandInvokedHandler(
        async ( cmd ) => await SenseHelper.LaunchSenseSettingsAsync() ) ) );
    
    dlg.Commands.Add( new UICommand( "No" ) );
    await dlg.ShowAsync();
}

Calling the SensorCore SDK safely

To call the SensorCore APIs safely, you must handle exceptions properly. The APIs throw exceptions if SensorCore is not supported by the device, or if required settings are not enabled.

We have provided a wrapper for you to use when calling SensorCore APIs which will take care of these things for you. You may have to fine tune the wrapper to suit your specific use case though. For example, if you are creating a biking application, you would want to make sure that the user has detailed data collection turned on in addition to having system location and Places visited settings enabled.

private async Task<bool> CallSensorcoreApiAsync( Func<Task> action )
{
    Exception failure = null;

    try
    {
        await action();
    }
    catch( Exception e )
    {
        failure = e;
    }

    if( failure != null )
    {
        MessageDialog dialog;

        switch( SenseHelper.GetSenseError( failure.HResult ) )
        {
            case SenseError.LocationDisabled:
                dialog = new MessageDialog( "Location has been disabled. Do you want to
                open Location settings now?", "Information" );
    
                dialog.Commands.Add( new UICommand( "Yes", async cmd => await
                SenseHelper.LaunchLocationSettingsAsync() ) );
    
                dialog.Commands.Add( new UICommand( "No" ) );
                await dialog.ShowAsync();
                new System.Threading.ManualResetEvent( false ).WaitOne( 500 );
                return false;
    
            case SenseError.SenseDisabled:
            {
                var settings = await SenseHelper.GetSettingsAsync();
        
                if( settings.Version < 2 )
                {
                     dialog = new MessageDialog( "Motion data has been disabled. Do you 
                                want to open motion data settings now?", "Information" );
                }
                else
                {
                     dialog = new MessageDialog( "Places visited has been disabled. Do you
                         want to open motion data settings now?", "Information" );
                }
                
                dialog.Commands.Add( new UICommand( "Yes", new
                        UICommandInvokedHandler( async ( cmd ) => await
                        SenseHelper.LaunchSenseSettingsAsync() ) ) );
                        
                dialog.Commands.Add( new UICommand( "No" ) );
                await dialog.ShowAsync();
                new System.Threading.ManualResetEvent( false ).WaitOne( 500 );
                return false;
            }
    
            default:
                dialog = new MessageDialog( "Failure: " + SenseHelper.GetSenseError(
                        failure.HResult ), "" );
                await dialog.ShowAsync();
                return false;
        }
    }

    return true;
}

To use the sensors, you must first instantiate them with the GetDefaultAsync method. You can call the initialization using the wrapper provided above like this:

if (await CallSensorcoreApiAsync( async () =>
{
    monitor = await Monitor.GetDefaultAsync();
} ) )

Activation and deactivation

With Universal apps, the navigation model differs from Silverlight, and you have to handle the activation and deactivation of the sensors differently. We are adding a handler for the VisibilityChanged event for when we activate and deactivate the Place Monitor when the application is put to background or when it gets focus again. This can be done with a traditional event handler as well. Below is a short way of doing the same thing.

Window.Current.VisibilityChanged += async( oo, ee ) =>
{
    if( !ee.Visible )
    {
        await CallSensorcoreApiAsync( async () =>
        {
            if( _sensor != null )
            {
                await _sensor.DeactivateAsync();
            }
        } );
    }
    else
    {
        await CallSensorcoreApiAsync( async () =>
        {
            if( _sensor != null )
            {
                await _sensor.ActivateAsync();
            }
        } );
    }
}

With Silverlight, you would do the same like this:

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    if (_stepCounter == null)
    {
        await Initialize();
    }
    await _stepCounter.ActivateAsync();
}
    
protected override async void OnNavigatingFrom(NavigatingCancelEventArgs e) 
{
    await _stepCounter.DeactivateAsync();
}

Error handling

Lumia SensorCore SDK APIs return errors by throwing exceptions. You can get more detailed information about the exception by using SenseHelper.GetSenseError.

Apps that utilize the SensorCore SDK can also be installed on devices that do not support SensorCore SDK features. Apps should prepare for cases in which no sensor or sensor data is available. If an app has no function without the SensorCore SDK, it should be stated clearly in the Store description.

One of the most important error situations is when a method fails because a user has disabled either location services or motion data collection. You should provide the user with an easy way of going to system settings to enable the necessary feature or features. You can, for example, encapsulate the API calls with the CallSenseApiAsync we defined earlier in the Getting started section to handle any errors.

You can then use that method for wrapping all SensorCore SDK related calls:

if( await CallSensorcoreApiAsync( async() =>
{
    _stepCounter = await StepCounter.GetDefaultAsync();
} ) )
{
    // Success
}
else
{
    // Failure
}
Show: