# Cortex iOS

## About the SDK

Welcome to the iOS Client SDK for [Medable](https://www.medable.com/). This framework provides a wrapper for the [Medable API](https://docs.medable.com/docs/api-reference) as well as several helper tools to directly integrate those features to your iOS application.

For basic integration and initialization steps, check the *README.md* file in the SDK repository.

### Examples

Check out the [Code Samples](https://docs.medable.com/docs/code-samples) for a few examples on how to get some specific things done with the Medable SDK.\
This tutorial shows one way to quickly get started using a new or existing Swift iOS project. Before you begin, you should have generated an API key. If not, [do this first](https://docs.medable.com/docs/generate-an-api-key).

### Create your Swift Project

In Xcode, select `File > New Project...`

\`\`![](https://files.readme.io/18e9b77-new-swift-project.png)

Select `Single View Application` and click next.

![](https://files.readme.io/2fb0196-new-swift-project-2.png)

Enter your project details. For this tutorial, we'll call the app "NewHealthCo"

### Install Medable Cortex iOS SDK

Integrate the SDK following the integration instructions from the *README.md* file in the SDK repository.

### Initialize Medable

Add the Medable start function to your AppDelegate and set the logger level to Debug so you can see more detailed network responses in the consoleSwift

```
//AppDelegate.swift

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    Medable.start()
    Medable.client().loggerLevel = MDAPIClientNetworkLoggerLevelDebug

    return true
}
```

Then build and run the app

The app shows a blank screen but you should see proper console outputs![](https://files.readme.io/3663424-response-200.png)

### Integration Complete

You've successfully connected to your Medable HIPAA compliant backend. You're ready to collect PHI.

## Pagination Helpers

When working with lists, it is often important to page through results. The recommended approaches to working with lists through the API are outlined [here](https://docs.medable.com/reference-link/paging).

However, the Cortex iOS SDK makes this quite simple with pagination helpers.

The following examples illustrate how to paginate through a list of `c_prescription` instances.

For information on how to create custom cortex objects such as the example `c_prescription` object, see [Data Model Setup](https://docs.medable.com/docs/data-model-setup).

For more information on working with your Cortex Objects in iOS, see [Cortex Objects](https://docs.medable.com/docs/custom-objects-context).

### 1. Using the Pagination Helper

This is how an instance of `MDPaginationHelper` is created and initialized:

Objective-C

```
MDPaginationHelper *prescriptions = [MDPaginationHelper
                                     paginatorWithContext:@"c_prescriptions"
                                     pageSize:10
                                     cacheResults:NO
                                     inverseOrder:NO];

// Optionally, custom API parameters (query parameters) can be specified; and the paginator will include those in the queries as well.
prescriptions.customParameters = [MDAPIParameterFactory parametersWithWhere:@{ @"c_refills": { @"$gt": 5 } } prefixPath:nil];

// set your class as the paginator's delegate if you use the delegate approach (see explanation below):
prescriptions.delegate = self;

// set the paginator's results callback block, if you use the callback block approach (see explanation below):
prescriptions.resultsCallback = ^(NSArray<MDObjectInstance *> * _Nullable objects, NSNumber * _Nullable hasMore, MDFault * _Nullable fault)
    {
        if (fault)
        {
            // fault handling
        }
        else
        {
            // "objects" will be instances of the Prescription class.

            // Use hasMore.boolValue to know if there are more pages to load
        }
    };

// And then you can call either `loadNextPage` or `loadAllPages` on the paginator, depending on what you need:
[prescriptions loadNextPage];

// or
[prescriptions loadAllPages];

// ----
// Note: make sure to store the instance somewhere, otherwise it will be destroyed by ARC.
```

If you use the *delegate* approach, don't forget to conform to the `MDPaginationHelperDelegate` protocol,

Objective-`C`

```
// YourClassHandlingPaginationResults.h

@interface YourClassHandlingPaginationResults <MDPaginationHelperDelegate>
  ...
@end
```

and implement the delegate methods to get the results:

Objective-C

```
// YourClassHandlingPaginationResults.m

@implementation YourClassHandlingPaginationResults

...

- (void)paginator:(MDPaginationHelper *)paginator didLoadResults:(nullable NSArray<MDObjectInstance*> *)objects hasMore:(nullable NSNumber *)hasMore fault:(nullable MDFault *)fault
{
    // handle your page results here
    if (fault)
    {
        // fault handling
    }
    else
    {
        // "objects" will be instances of the Prescription class.

        // Use hasMore.boolValue to know if there are more pages to load
    }
}

- (void)paginator:(MDPaginationHelper *)paginator didLoadAllResults:(nullable NSArray<MDObjectInstance*> *)objects fault:(nullable MDFault *)fault
{
    // handle "all the pages" results here
    if (fault)
    {
        // fault handling
    }
    else
    {
        // "objects" will be instances of the Prescription class.
    }
}

...

@end
```

Both delegate methods are marked as *optional*. If you don't implement the `didLoadAllResults`, and `loadAllPages` is called, you'll get all the results in the first method, together with the results from calling `loadNextPage`. So, if you need to get the results of both calls separately, implement both.

> #### 📘Note
>
> The *delegate approach* and the *results callback approach* can be used simultaneously.
>
> #### 📘Note
>
> You'll find documentation about `cacheResults` and `inverseOrder`, as well as for some other pagination options (e.g. list property pagination, or paging using a field other than `_id`), in the `MDPaginationHelper.h` header file.

### 2. Using the Pagination Manager

If you don't want to manage the instances of `MDPaginationHelper`, you can leave that to the `MDPaginationManager`. Just create the instances using the `MDPaginationManager` class. Let's achieve the same as before; the only difference relies in how you create the instance:

Objective-C

```
// With this identifier, you can retrieve the instance from the MDPaginationManager later...
NSString *identifier = @"someIdentifier";

MDPaginationHelper *prescriptions = [MDPaginationManager
                                     paginatorWithIdentifier:identifier
                                     context:@"c_prescriptions"
                                     pageSize:10
                                     cacheResults:NO
                                     inverseOrder:NO];

// the rest is the same, and you don't have to take care of storing the paginator
```

Later on, that paginator's instance can be retrieved by doing this:

Objective-C

```
MDPaginationHelper *prescriptions = [[MDPaginationManager sharedInstance] paginatorWithIdentifier:@"someIdentifier"];
```

If you don't need to retrieve the pagination helpers by identifier, but you still want the manager to take care of storing them; there is a function to generate random identifiers:

Objective-C

```
NSString *randomIdentifier = [MDPaginationManager randomId];
```

## Code Samples

### Login

Objective-C

```
[[Medable client]
 loginWithEmail:@"your email here"
 password:@"your password here"
 verificationToken:nil
 singleUse:NO
 callback:^(MDAccount * _Nullable localUser, MDFault * _Nullable fault)
 {
     if (fault)
     {
         // handle fault
     }
 }];
```

### Logout

Objective-C

```
[[Medable client] logout:^(MDFault * _Nullable fault)
 {
     if (fault)
     {
         // handle fault
     }
 }];
```

### Register User

Objective-C

```
[[Medable client]
 registerAccountWithFirstName:@"Charles"
 lastName:@"Best"
 email:@"charles.best@example.org"
 mobile:@"1-650-555-5555"
 password:@"Thanks for the break, Banting!"
 gender:@"m"
 dob:[MDDateUtilities dateFromString:@"1899-02-27"]
 role:nil
 profileInfo:nil
 thumbImage:nil
 timeZone:nil
 customPropValues:nil
 callback:^(MDAccount* account, MDFault *fault)
 {
     if ( fault != nil)
     {
         // Handle the error (e.g. show alert with fault.message)
     }
     else
     {
         // Check whether activation is required before authentication
         BOOL requiresActivation = [account activationRequired] ? [[account activationRequired] boolValue] : NO;

         // Handle the case when requiresActivation == YES here
     }
 }];
```

### Create Object

Let's suppose we are using a custom `Prescription` object definition that was created using the [Data Model Setup](https://docs.medable.com/docs/data-model-setup) guide.

Now, we want to create instances of those objects using Cortex from the client side.\
Objective-C

```
NSDictionary *prescriptionBody = @{
                                   @"c_date": [NSDate new],
                                   @"c_dispense": @3,
                                   @"c_patient": @"some patient Id reference",
                                   @"c_provider": @"some provider Id reference",
                                   @"c_refills": @2,
                                   @"c_rx": @"Prescription details"
                                   };

[[Medable client]
 createObjectWithContext:@"c_prescription"
 body:prescriptionBody
 callback:^(MDObjectInstance * _Nullable instance, MDFault * _Nullable fault)
 {
     if (fault)
     {
         // handle fault
     }
     else if (instance)
     {
         // newly created instance
     }
 }];
```

### Edit Object

Let's suppose we are using a custom `Prescription` object definition that was created using the [Data Model Setup](https://docs.medable.com/docs/data-model-setup) guide.

Now, we want to edit an existing instance of that type of object using Cortex from the client side.\
Objective-C

```
NSDictionary *propertiesToUpdate = @{
                                     @"c_dispense": @7,
                                     @"c_refills": @9,
                                     @"c_rx": @"Updated prescription details"
                                     };

[[Medable client]
 updateObjectWithContext:@"c_prescription"
 objectId:[MDObjectId objectIdWithString:@"the object's Id"]
 body:propertiesToUpdate
 callback:^(MDObjectInstance * _Nullable instance, MDFault * _Nullable fault)
 {
     if (fault)
     {
         // handle fault
     }
     else if (instance)
     {
         // edited instance
     }
 }];
```

### List Objects

Let's suppose we are using a custom `Prescription` object definition that was created using the [Data Model Setup](https://docs.medable.com/docs/data-model-setup) guide.

Now, we want to list existing instances of that type of object using Cortex from the client side.

> #### 📘Note
>
> For paginated listing take a look the [Pagination Helpers](https://docs.medable.com/docs/pagination-helpers) guide.

Objective-C

```
[[Medable client]
 listObjectsWithContext:@"c_prescription"
 parameters:nil
 callback:^(NSArray<MDObjectInstance *> * _Nullable objects, NSNumber * _Nullable hasMore, MDFault * _Nullable fault)
 {
     if (fault)
     {
         // handle fault
     }
     else
     {
         // hasMore is true if there are more objects to load --there's a limit in the amount of returned objects.
         // objects contains the returned object instances
     }
 }];
```

> #### 📘Note
>
> If you need to use a custom class instead of `MDObjectInstance` and want Cortex to return instances of your class instead, follow the [Cortex Objects](https://docs.medable.com/docs/custom-objects-context) guide.

### Delete Object

Objective-C

```
[[Medable client]
 deleteObjectWithContext:@"c_prescription"
 objectId:[MDObjectId objectIdWithString:@"the object's Id"]
 reason:nil
 callback:^(MDFault * _Nullable fault)
 {
     if (fault)
     {
         // handle fault
     }
 }];
```

### File Download

[File](https://docs.medable.com/reference-link/files)s can be downloaded using one of two methods.

Using a download path:\
Objective-C

```
NSString *filePath = [MDAPIClient routeWithComponents:@[ @"path", @"to", @"file" ]];

[MDAPIClient
 downloadFileAtPath:filePath
 callback:^(id  _Nullable streamData, MDFault * _Nullable fault)
 {
     if (fault)
     {
         // handle fault
     }
     else if ([streamData isKindOfClass:[UIImage class]])
     {
         // downloaded file is an instance of UIImage
     }
     else if ([streamData isKindOfClass:[NSData class]])
     {
         // downloaded file is an instance of NSData
     }
 }];
```

Or using a `MDFacet` of the [File](https://docs.medable.com/reference-link/files).\
Objective-C

```
MDFacet *aFileFacet = ...;

[aFileFacet fileDataWithCallback:^(NSData * _Nullable data, MDDataSource source, MDFault * _Nullable fault)
 {
     if (fault)
     {
         // handle fault
     }
     else if ([data isKindOfClass:[UIImage class]])
     {
         // downloaded file is an instance of UIImage
     }
     else if ([data isKindOfClass:[NSData class]])
     {
         // downloaded file is an instance of NSData

         // Use source to query the source of the data
         switch (source)
         {
             case MDDataSourceCache:
                 // data comes from local cache
                 break;
             case MDDataSourceRemote:
                 // data was just downloaded
                 break;
             default:
                 //
                 break;
         }
     }
 }];
```

### File Upload

File uploads are a two step process as described in the [File Uploads](https://docs.medable.com/docs/file-uploads) documentation.

1. In the first step, a file property name and the file name is sent to the server. This could happen when [creating an object](https://docs.medable.com/docs/code-samples-2#section-create-object) and also when [editing an object](https://docs.medable.com/docs/code-samples-2#section-edit-object).
2. In the response of the first step, that is, in the callback of whichever method was used in step 1 --create or update; is when the actual file upload takes place using upload information sent by the Cortex API.

For this sample, let's suppose we are creating an instance of a `c_anObject` object type that, has a `c_image` [File](https://docs.medable.com/reference-link/files) property. We want to upload an image to the `content` facet of that [File](https://docs.medable.com/reference-link/files).\
Objective-C

```
// STEP 1 - Create/Update the object and send a file property key/value pairs
UIImage *fileToUpload = ...;
// Note: fileToUpload can be an instance of NSData too.

// File values are always key = facet name, value = file name.
NSDictionary *filePropValue = @{ @"content": fileToUpload };

// --- Optinally the mime type can be set:
NSDictionary *filePropValue = @{ @"content": @{ @"mime": @"image/png", @"data": fileToUpload }};
// ---

// Create/Update bodies are always key = prop name, value = prop value or the right type.
NSDictionary *createBody = @{
                             @"c_aPropName": @"aPropValue",
                             @"c_image": filePropValue,
                             @"c_someOtherPropName": @55
                             };

[[Medable client]
 createObjectWithContext:@"c_anObject"
 body:createBody
 callback:^(MDObjectInstance * _Nullable instance, MDFault * _Nullable fault)
 {
     if (fault)
     {
         // handle fault
     }
     else if (instance)
     {
         // STEP 2 - File Upload - Performed internally by Cortex. Read "Managing File Uploads" to know how to keep track of file uploads. Files are also cached and encrypted locally by Cortex.
     }
 }];
```

> #### 📘File Uploads
>
> For a deeper management of file uploads take a look at the [Managing File Uploads](https://docs.medable.com/docs/managing-file-uploads) documentation.

## Managing File Uploads

The Medable Cortex iOS SDK features an upload operations manager, that takes care of the following tasks:

* Keep an up to date list of ongoing upload operations.
* Keep an up to date list of recently successfully completed operations.
* Keep an up to date list of recently failed operations.
* Keep a progress value (percentage expressed in the 0..1 range) for each operation.
* Emit notifications for every change in the manager: - Newly added upload. - Upload completed or failed. - Upload operation progress changed.

### Notifications

Every time there is a significant event in the upload operations manager, it emits a notification using the string `kOperationProgressChangedNotification`, which is declared in `MDUploadOperations.h`:\
Objective-C

```
/**
 * String used to notify that an upload operation has changed its state.
 *
 * This can happen when the upload operation is started, its progress has changed
 * or when it completes.
 */
extern NSString *const kOperationProgressChangedNotification;
```

As expressed in the comment, this notification is emitted for every change in the upload operations manager.

How to **listen** the notifications:\
Objective-C

```
[[NSNotificationCenter defaultCenter]
 addObserver:self
 selector:@selector(handleUploadNotification:)
 name:kOperationProgressChangedNotification
 object:nil];
```

How to **handle** the notifications:\
Objective-C

```
- (void)handleUploadNotification:(NSNotification *)notification
{
    if ([notification.object isKindOfClass:[MDUploadOperation class]])
    {
        MDUploadOperation *uploadOperation = (MDUploadOperation *)notification.object;

        NSString *fileName = uploadOperation.fileName;
        NSNumber *progress = uploadOperation.progressNumber;

        NSLog(@"Uploading file: %@ Progress: %f", fileName, progress.floatValue);
    }
    else if ([notification.object isKindOfClass:[MDUploadOperations class]])
    {
        // Ongoing
        NSArray<MDUploadOperation *> *ongoingUploads = [MDUploadOperations ongoingOperations];

        // Completed
        NSArray<MDUploadOperation *> *completedUploads = [MDUploadOperations completedOperations];

        // Failed - these can be retried while the upload token is still valid. Upload tokens have an expiry date.
        NSArray<MDUploadOperation *> *failedUploads = [MDUploadOperations failedOperations];
    }
}
```

Within the notification, depending on the event, two different objects could be sent:

* A `MDUploadOperation` object is delivered inside the `NSNotification.object` property if it is notifying about upload progress. See below for more info.
* A `MDUploadOperations` object is delivered inside the `NSNotification.object` property if it is notifying about the following events:
  * A new `MDUploadOperation` was added to the queue.
  * A `MDUploadOperation` succeeded.
  * A `MDUploadOperation` failed.
  * A `MDUploadOperation` was removed from completed/failed.
  * Flushing operations. Flushing ongoing, succeeded and failed queues.

### State

The state of all upload operations can be queried from the `MDUploadOperations` class by checking the the following **class methods**:\
Objective-C

```
+ (NSArray<MDUploadOperation *> *)ongoingOperations;

+ (NSArray<MDUploadOperation *> *)completedOperations;

+ (NSArray<MDUploadOperation *> *)failedOperations;
```

An ongoing operation is an upload task that is still uploading, i.e. it hasn't failed and it hasn't completed the upload yet.

Recently completed operations are short lived. After completion, the operation objects will stay in this list for at least 10 seconds. After this time, they'll be removed from the list.

Recently failed operations live a little longer. After failing, the operation objects will stay in this list for at least 1 minute. Again, after this time, they are be removed from the list.

Each operation is an instance of class `MDUploadOperation`. Here are some interesting features they provide:

* **Progress**: Use the `progressNumber` attribute to determine the progress percentage (measured in the 0..1 range) of the upload operation.
* **Data Task**: Use the `operation` property, of class `NSURLSessionDataTask` for the data task that the operation is using. You may call `cancel` on this object to cancel the upload.
* **File Name**: If you are uploading several things at once, the `fileName` property contains the name of the file being uploaded.
* **NSProgress**: Apple's `NSProgress` class allows for fine grain progress reporting, the `operationProgress` property returns the associated `NSProgress` object with this upload operations.

### Cancel an Ongoing Operation

To cancel an ongoing operation, just call `cancel` on its data task, which you get from calling `operation` on the `MDUploadOperation` instance.

Here is code to cancel an ongoing operation:\
Objective-C

```
MDUploadOperation *uploadOperation = [[MDUploadOperations ongoingOperations] firstObject];
[uploadOperation.operation cancel];
```

### Retry a Failed Operation

Any operation that has failed can be retried, it need not be in the failed operations set, but beware that the upload tokens used will eventually expire.

Here is code to retry an operation currently in the failed set:\
Objective-C

```
MDUploadOperation *failedUploadOperation = [[MDUploadOperations failedOperations] firstObject];
[MDUploadOperations retryOperation:uploadOperation];
```

## Migration Guide

If you're upgrading from a previous version to 1.10 or newer, you will need to make the following changes:

* Remove the Medable framework from the Xcode project.
* Following the [installation guide](https://docs.medable.com/docs/ios-sdk), update to the latest version.
* Replace any instance of `Medable/Medable.h` with `MedableCortex/MedableCortex.h`
