Xamarin iOS Framework Wrapper

validic_xamarin_ios_1.14.1

❗️

Xamarin End of Support coming May 1, 2024

Microsoft will end support for all Xamarin SDKs, including Xamarin.Forms, on May 1, 2024.

First, note that your Xamarin apps will remain available in the Apple App Store and Google Play Store after Microsoft's May 1, 2024 support cut-off. Apps won't be removed from the stores, and they will continue working as they have.

However, if you are currently using the Validic Mobile Xamarin wrappers and you need to continue updating and/or using your Xamarin apps, then you should reach out to your Validic Client Success Executive or the Validic Support team immediately for guidance on other options.

You can find additional information on Microsoft's website.


To get started, download the Xamarin iOS framework from the Validic Mobile portal.

  • The Validic Mobile library supports iOS 14 and above.
  • Requires Xamarin Studio.

Installation


Add Validic dynamic-link libraries to your project’s References. In Xamarin Studio, double click References in the left panel. In the .Net Assembly tab click Browse and select ValidicCore.iOS.dll, ValidicBluetooth.iOS.dll, ValidicHealthKit.iOS.dll, ValidicOCR.iOS.dll.

You should now see ValidicMobile.iOS under “References” in the panel on the left.

Permission Usage Descriptions

Usage descriptions have to be declared in project’s Info.plist for permissions of the Camera, Bluetooth and to access and update HealthKit data.

  • The OCR component uses the camera and requires filling in the Privacy - Camera Usage Description key and value if edited in Xcode, or NSCameraUsageDescription if viewed as XML.

  • The HealthKit component requires filling in the Privacy - Health Share Usage Description key and value if edited in Xcode, or NSHealthShareUsageDescription if viewed as XML. It also requires filling in the Privacy - Health Update Usage Description key and value if edited in Xcode, or NSHealthUpdateUsageDescription if viewed as XML.

  • The Bluetooth component requires filling in the Privacy - Bluetooth Peripheral Usage Description key and value if edited in Xcode, or NSBluetoothPeripheralUsageDescription if viewed as XML.

If these values are not provided, the app will crash at runtime with an error reported to the console log.

Native Logging

The Validic Mobile library includes extra logging that can be enabled in debug or production app builds to get further insights into behavior. To enable logging, use VLDLogging, making sure to set both IsEnabled and the Log mask property. You should also call SetLogger with a lambda or delegate function in order to do something with the logs.

VLDLogging.SharedInstance().IsEnabled = true;
VLDLogging.SharedInstance().Log = VLDLogMask.Core | VLDLogMask.Bluetooth | VLDLogMask.HealthKit | VLDLogMask.Ocr;
VLDLogging.SharedInstance().SetLogger((String sdkTag, String message) => Console.WriteLine(sdkTag + " " +  message));

Session


Provision a User

The Validic Mobile library requires a Validic user.

Validic user can be provisioned using the Validic API, documentation is available for both Legacy API (V1) and Inform API (V2) APIs. The credentials needed are a Validic user ID, an organization ID and a user access token. The user access token value you provide to the SDK is the "user access token" value if you use the Legacy (V1) API. The user access token value you provide to the SDK is the "mobile token" value if you use the Inform (V2) API.

Record types

The Validic Mobile library supports the following record types:

  • VLDBiometrics - comprised of a user’s biometric health data such as blood pressure, cholesterol, heart rate, and blood and hormone levels.
  • VLDDiabetes - comprised of a user’s blood glucose and hormone levels related to diabetes treatment and management.
  • VLDFitness - comprised of a user’s activities that are undertaken with the express purpose of exercising. These activities have a defined duration (time, distance, elevation, etc.)
  • VLDNutrition - comprised of a user’s activities related to calorie intake and consumption and nutritional information (such as fat, protein, carbohydrates, sodium, etc.)
  • VLDRoutine - comprised of a user’s activities that occur regularly throughout the day, without the specific goal of exercise, for example calories burned and consumed, steps taken, stairs climbed. These activities are aggregate throughout the day.
  • VLDSleep - comprised of a user’s measurements related to the length of time spent in various sleep cycles, as well as number of times woken during the night.
  • VLDWeight - comprised of a user’s weight and body mass.

Record Identifiers

The Validic Mobile SDK uses log_id (for the Inform/V2 API) or activity_id (for the Legacy/V1 API) as the unique identifier for a given record. These IDs are created by the mobile SDK before submission to the server.

The Validic server also assigns an id (for the Inform/V2 API) or _id (for the Legacy/V1 API) for each unique record. It is recommended to use the server-side id / _id to identify and/or de-duplicate records post-submission.

Manage a Session

Overview

VLDSession stores a user, their current HealthKit subscriptions and all pending record uploads. This data is persisted between app launches but is deleted if endSession is called.

VLDSession is a singleton object and must be accessed by its SharedInstance method. The different components of the Validic Mobile library rely on a valid user existing in the current VLDSession singleton object.

To start a VLDSession you will need a VLDUser object. Generally you retrieve the credentials to instantiate a user object from your server. The credentials needed are a Validic user ID, an organization ID and a user access token.

VLDUser user = new VLDUser ("user_id", "organization_id", "access_token");

The user object is then passed to the StartSessionWithUser() method. Note: starting a session will delete the current session if it exists.

VLDSession.SharedInstance().StartSessionWithUser (user);

Notifications

When VLDSession uploads a record it will send an NSNotification. To listen for this notification add an observer to the NSNotificationCenter with the string Constants.kVLDRecordSubmittedNotification. The object property of the notification will contain the VLDRecord object uploaded.

When VLDSession uploads an image, it will send a different notification, Constants.kVLDRecordImageSubmittedNotification. The object property of the notification will contain the VLDRecord object with the media property populated.

If a 400 error is returned from the server, the record will be discarded and an NSNotification will be posted. To listen for this notification add an observer to the NSNotificationCenter with the constant Constants.kVLDRecordSubmissionFailedNotification. The object property of the notification will contain the invalid VLDRecord object. The userInfo dictionary for this notification will contain one key, error with the NSError object for the failed upload.

If VLDSession attempts to upload the image associated with a record and the upload fails, an NSNotification, Constants.kVLDRecordImageSubmissionFailedNotification will be posted.

Bluetooth


Peripherals

VLDBluetoothPeripheral represents Bluetooth peripheral models that can be discovered or read from by VLDBluetoothPeripheralController.

A peripheral object contains various information to be displayed to the user:

  • name - Name of the peripheral comprised of the the manufacturer name and model number.
  • pairingInstructions - Text telling the user how to pair the peripheral, if the peripheral requires pairing.
  • instructions - Text telling the user how to initialize the reading process with the peripheral.
  • readingInstructions - Text telling the user what to do during the reading process.
  • imageURL - URL for an image of the peripheral.

Supported Peripherals

To retrieve a List of supported peripherals simply call:

foreach (VLDBluetoothPeripheral peripheral in VLDBluetoothPeripheral.SupportedPeripherals)
{
    Console.WriteLine(peripheral.Name);
}

Other methods are available on VLDBluetoothPeripheral to allow retrival of a specific peripheral, or to retrieve a list of peripherals of a specific peripheral type. See the VLDBluetoothPeripheral documentation for additional information.

Pair

Certain peripherals require pairing with the mobile device before a reading can be taken. You can check the requiresPairing property on the VLDBluetoothPeripheral object to know if it must be paired.

To pair a peripheral with the mobile device, call the PairPeripheral() method on VLDBluetoothPeripheralController.

VLDBluetoothPeripheral peripheral = // A peripheral from the supportedPeripherals list
VLDBluetoothPeripheralController controller = new VLDBluetoothPeripheralController();
controller.WeakDelegate = this;

controller.PairPeripheral(peripheral);

To know if a peripheral was successfully paired you will need to implement the pairing methods of the VLDBluetoothPeripheralControllerDelegate protocol.

[Export("bluetoothPeripheralController:didPairPeripheral:")]
public void BluetoothPeripheralControllerDidPairPeripheral (VLDBluetoothPeripheralController controller, VLDBluetoothPeripheral peripheral)
{
    // Peripheral paired successfully
}

[Export("bluetoothPeripheralController:didNotPairPeripheral:error:")]
public void BluetoothPeripheralControllerDidNotPairPeripheral(VLDBluetoothPeripheralController controller, VLDBluetoothPeripheral peripheral, NSError error)
{
    // Peripheral did not pair
}

Read

Once you are ready to read from a peripheral, the process is fairly similar to the pairing process. You’ll want to first show peripheral.Instructions and eventually show peripheral.ReadingInstructions once BluetoothPeripheralControllerIsReadyToReadFromPeripheral() is called.

VLDBluetoothPeripheralController controller = new VLDBluetoothPeripheralController ();
controller.WeakDelegate = this;

controller.ReadFromPeripheral (peripheral);

You must also implement the reading methods of the VLDBluetoothPeripheralControllerDelegate protocol.

[Export("bluetoothPeripheralController:isReadyToReadFromPeripheral:")]
public void BluetoothPeripheralControllerIsReadyToReadFromPeripheral (VLDBluetoothPeripheralController controller, VLDBluetoothPeripheral peripheral)
{
    // Time to present the readingInstructions to the user
}

[Export("bluetoothPeripheralController:shouldSubmitReadings:fromPeripheral:")]
public bool BluetoothPeripheralControllerShouldSubmitReadings (VLDBluetoothPeripheralController controller, VLDRecord[] records, VLDBluetoothPeripheral peripheral)
{
    // To have the reading automatically added to the session and uploaded
    // return true from this method. To first have the reading validated by the user
    // you can return false from this method. Once the user has validated the reading
    // you must manually submit the record by calling VLDSession.SharedInstance().submitRecords(record);
    return true;
}

[Export("bluetoothPeripheralController:readingFailedForPeripheral:error:")]
public void BluetoothPeripheralControllerReadingFailed (VLDBluetoothPeripheralController controller, VLDBluetoothPeripheral peripheral, NSError error)
{
    // Reading failed
}

Passive Read

The ValidicMobile library supports passively reading from a given set of Bluetooth peripherals. Passive Bluetooth reading is a long lived capability. Once enabled, the library will listen for and read from the specified peripherals. Reading will automatically occur once the peripheral is discovered regardless of app state. This includes if the application is currently in the foreground, in the background, or has been terminated due to memory pressure. Apps using this feature must have been started by the user at least once since the iOS device has booted otherwise the feature will not be active even if enabled.

VLDBluetoothPassiveManager is a singleton which coordinates the passive reading process. Passive reading is enabled by passing it an array of peripheral IDs:

VLDBluetoothPassiveManager.SharedInstance().PeripheralIDs = new NSNumber[] { 1, 2 };

To disable passive reading, set the peripheralIDs property to an empty array or nil.

VLDBluetoothPassiveManager.SharedInstance().PeripheralIDs = null;

Xcode Project changes

Passive Bluetooth reading relies upon iOS Bluetooth state restoration and background processing support. Apps using passive reading need to set Bluetooth as a required background mode in the Info.plist. In Xcode, in the Info.plist add a new key, Required background modes, add an item with value App communicates using CoreBluetooth.

If editing the XML source file, add Bluetooth-central background mode as follows:

    <key>UIBackgroundModes</key>
    <array>
        <string>Bluetooth-central</string>
    </array>

To support state restoration, a method on the passive manager must be called on app launch. This should be done in the application:didFinishLaunchingWithOptions: method of the AppDelegate as follows:

        public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
        {
            // Bluetooth passive support if launched to handle discovered peripheral
            VLDBluetoothPassiveManager.SharedInstance().RestoreState();
            return true;
        }

State restoration will automatically start the app in the background when a requested peripheral is discovered. Within application:didFinishLaunchingWithOptions:, apps can determine if they were launched in the background and avoid expensive operations.

Notifications

Passive readings are automatically sent to the Validic server. Session notifications are sent on upload success or failure. Passive Bluetooth specific notifications are sent when a device is discovered and becomes ready to read. Notifications are also sent for reading success and failure. These notifications specify the associated peripheral ID in the userInfo dictionary with a key, peripheralID.

  • kVLDBluetoothPassiveDidReadNotification is sent when records are read. The notification object is an array of VLDRecord read from the peripheral.
  • kVLDBluetoothPassiveDidFailNotification is sent when failing to read from a discovered peripheral.
  • kVLDBluetoothPassiveIsReadyToReadNotification is sent when the peripheral becomes ready to read. This notification is only sent out for devices that are discoverable before a measurement is taken. It is important to handle this notification in an app and provide feedback to the user that the device has been discovered and is ready to take a measurement. Since Passive Bluetooth reading is usually performed in the background, a standard UI is not available. A local notification can be sent with sound to provide feedback to the user that the app is ready to read from the peripheral. If the user doesn’t hear the sound, it indicates that the device has not been discovered.

Considerations

There are situations where passive Bluetooth reading is stopped and requires the app to be relaunched. These situations include:

  • When the user explicitly kills the app by swiping up on the app switcher view, the app will not be restarted by iOS when a peripheral is discovered.
  • If the iOS device is rebooted, state restoration is no longer in effect and iOS will not automatically restart the app when a peripheral is discovered. The app needs to be run at least once after reboot. When the app is launched the passive manager remembers what peripherals if any where being passively read and restarts the capability.

Passive Bluetooth reading has some additional considerations due to the behavior of the iOS Bluetooth stack and background support.

  • Interactive, ie non-passive, Bluetooth operations will suspend passive Bluetooth processing until the interative operation completes.
  • When scanning for peripherals in the background, iOS reduces the frequency and duration of time the phone listens for devices. This may cause some peripherals to not be discovered in the background. This varies by phone model and peripheral. Generally older phone models are more likely to miss discovery of a peripheral.
    • Nonin pulse oximeters, ChoiceMMed pulse oximeter and scale are not usually detectable when in the background.
    • The Pyle thermometer is sometimes not discovered on older phone models when in the background.
  • iOS has additional heuristics to determine scanning frequency which may not be documented. If multiple apps on the phone are performing background scanning, scanning may become more infrequent.
  • To prevent repeated attempted reads from a device for one actual reading, the passive manager waits after a successful read until the device stops broadcasting, then waits an additional few seconds before acknowledging the device again. If attempting multiple readings in quick succession, the second reading may not be read. Instead the user should wait until the device powers off and then wait an additional 15 seconds or longer before attempting another reading.

HealthKit


The Validic Mobile library provides a simple way to read and upload data from HealthKit to Validic. VLDHealthKitManager can subscribe to specific HealthKit sample types and automatically upload them to Validic in the background as new data is recorded.

Subscribing to HealthKit updates only needs to be done once for a user. The subscriptions will be persisted across app launches in the VLDSession object. A typical use of the HealthKit component would be to create a UISwitch that adds the required subscriptions when turned on and removes them when turned off.

To properly process the delivery of data in the background, the subscription observers need to be recreated immediately when the app is launched. To do this, you need to call VLDHealthKitManager.SharedInstance().ObserveCurrentSubscriptions(); inside your application delegate’s FinishedLaunching() function. Example:

public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
{
    VLDHealthKitManager.SharedInstance().ObserveCurrentSubscriptions();
    return true;
}

Note: Calling VLDSession.SharedInstance().EndSession(); will remove all HealthKit subscriptions and stop listening for new data.

Set subscriptions

You can set subscriptions by using the subscriptions provided. If you’re using a single subscription set by passing it directly to setSubscriptions. Example:

var routine = VLDHealthKitSubscription.SampleTypesForSubscriptionSet(VLDHealthKitSubscriptionSet.Routine);
VLDHealthKitManager.SharedInstance().SetSubscriptions(routine, () =>
{
    Log("Subscribed routine subscription set");
});

If you need to set multiple subscription sets you combine the subscription sets into a NSMutableArray. Example:

var routine = VLDHealthKitSubscription.SampleTypesForSubscriptionSet(VLDHealthKitSubscriptionSet.Routine);
var fitness = VLDHealthKitSubscription.SampleTypesForSubscriptionSet(VLDHealthKitSubscriptionSet.Fitness);

var mutableArray = new NSMutableArray();
for (nuint i = 0; i < routine.Count; i++)
{
    var sample = routine.GetItem<HKQuantityType>(i);
    mutableArray.Add(sample);
}
for (nuint i = 0; i < fitness.Count; i++)
{

    var sample = fitness.GetItem<HKQuantityType>(i);
    mutableArray.Add(sample);
}

VLDHealthKitManager.SharedInstance().SetSubscriptions(mutableArray, () =>
{
    Log("Subscribed routine fitness subscription sets");
});

Note

GetItem defines the objects inside routine and fitness as an array of HKQuanityType because a concrete type must be used. Using the abstract type HKSampleType here will cause a crash at runtime.

Fetch historical data

The Validic Mobile library provides the ability to query up to 180 days of historical data for a subset of data types provided by HealthKit by specifying one of more values of the VLDHealthKitHistoricalSet enum.

Two historical sets are available:

  • VLDHealthKitHistoricalSetRoutine - Step data
  • VLDHealthKitHistoricalSetFitness - Workout data
  • VLDHealthKitHistoricalSetIntraday - Intraday step data (Premium feature, with only 7 days of historical data)

To fetch historical HealthKit data, call the fetchHistoricalSets() method on VLDHealthKitManager and pass in the datasets you want to fetch, along with from and to parameters.

The from and to parameters should be NSDateComponents with the year, month, and day components defined. Data is always pulled in full-day increments, so any time data on the NSDateComponents (hour, minute, second, etc) will be ignored. Historical data cannot go back more than 179 days before the current day.

Example pulling 30 days of history:

var units = NSCalendarUnit.Year | NSCalendarUnit.Month | NSCalendarUnit.Day;
var end = NSDate.Now;
var start = end.AddSeconds(-86400*29);

var from = NSCalendar.CurrentCalendar.Components(units, start);
var to = NSCalendar.CurrentCalendar.Components(units, end);

VLDHealthKitManager.SharedInstance().FetchHistoricalSets(new NSNumber[] { 0 }, from, to, (DictionaryContainer, error) =>
{
    if (error != null)
    {
        Log(error.ToString());
        return;
    }
    Log("Fetched History");
});
    partial void HistoryButton_TouchUpInside(UIButton sender)
    {

        var to = NSDate.Now;
        var from = to.AddSeconds(-86400*30);

        VLDHealthKitManager.SharedInstance().FetchHistoricalSets(new NSNumber[] { 0 }, from, to, (DictionaryContainer, error) =>
        {
            if (error != null)
            {
                Log(error.ToString());
            }
            Log("Fetched History");
        });
    }

VitalSnap (OCR)


OCR provides the capability to obtain readings from devices without requiring Bluetooth or HealthKit integration. The simplest way to use this feature is to present an instance of the VLDOCRViewController after initializing it with the ID of a VLDOCRPeripheral.