Skip to content

This is a sample project in Swift that demonstrates how to build a complete application using the AppConnect SDK.

Notifications You must be signed in to change notification settings

mdsol/appconnect-ios

Repository files navigation

AppConnectSwift

AppConnectSwift is an example iOS app, written in Swift, that showcases proper usage of the AppConnect SDK. The functionality of AppConnect is contained within a library called Babbage, an homage to the father of the computer.

See Medidata AppConnect Home for more information.

Prerequisites

If you are running this application, it is assumed that:

  • You have a valid Rave installation with Patient Cloud functionality enabled.[if you are using Rave-enabled forms]

You also need permission to access Rave studies and sites. If you do not have these permissions, contact your Medidata representative for more information. [When using a Rave based Study]

Setup

Run pod install to install the necessary dependencies. When the CocoaPods have finished installing, open AppConnectSwift.xcworkspace and click "Run." The app should build and run successfully.

Architecture

The application contains the following important view controllers

  • LoginViewController - Handles user authentication based on a provided username / password.
  • EmailViewController - Handles user entry of the email address used to create a new account.
  • PasswordViewController - Handles user entry of the password used to create a new account.
  • SecurityQuestionViewController - Handles user selection of the security question used to create a new account.
  • CreateAccountViewController - Handles user entry of the security answer used to create a new account.
  • FormListViewController - Loads and displays the available forms for authenticated users.
  • OnePageFormViewController - Loads and displays a form on a single screen, providing validation before users submit forms.
  • MultiPageFormViewController - Loads and displays a form one field at a time. Uses the FieldViewController to display the fields.
  • CaptureImageViewController - Allows the user to take a picture or select one from the gallery, and uploads the image to AWS S3.
  • ReviewController - Allows user to review their answers before submitting forms.

Using the Case Report Form (CRF)

The AppConnect SDK comes with a sample CRF - SampleCRF.xls - that contains the two forms used in the sample app.

To use this sample form:

  1. Import the linked CRF into Rave for a subject of your choosing.
  2. Log in with the sample app using the credentials of the subject you chose.

You should see two forms, Form 1 and Form 2. Form 1 opens as one page. Form 2 opens as multiple pages.

Using Self-registration and Data Capture Functionality

  1. Uses the in-app registration to enroll a new user.
  2. Login with the user created above.
  3. Demo app shows an Image Capture form to take a picture or load an image, which will be uploaded to AWS S3

Using the API in Your Own Application

This is a guide to the basics of Babbage - initialization, making network requests, and loading data from the datastore.

Installation

To install Babbage, include it in your Podfile and run the following command:

pod install

Initialization

In Swift / Objective-C, Babbage must be initialized with two arguments. The first is a directory in which to store data. The second is a 32-byte key used to encrypt sensitive information. This key must be unique for each installation, and remain the same on each launch.

// In AppDelegate.swift
let dir = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).last
let key = "12345678901234567890123456789012".dataUsingEncoding(NSUTF8StringEncoding)
MDBabbage.startWithEnvironment(MDClientEnvironment.Sandbox, apiToken: "Your provided API token", publicDirectory: dir, privateDirectory: dir, encryptionKey: key)

Self-Registering the User

You can create an account for storing data to AWS S3 buckets.

// In CreateAccountViewController.swift

// fetch the security questions and filter out those that are deprecated
// The returned JSON is structured:
// {
//  "security_questions" : [
//      {
//          "id": 1,
//          "name" : "What year were you born?",
//          "deprecated" : "true"
//      },
//      ...
//  ]
// }

var validQuestions = [[String: AnyHashable]]()

client.loadSecurityQuestions() { (response: [AnyHashable: Any]?, error: Error?) -> Void in
        guard error == nil,
        let questions = response?["security_questions"] as? [[String: AnyHashable]] else {
            return
        }

        for question in questions {
            guard ((question["deprecated"] as? Bool) ?? false) == false else {
                continue
            }

            validQuestions.append(question)
        }
    }
}

let userEmail = "newuser@mdsol.com"
let userPassword = "Password1"  
let userSecurityQuestionID = validQuestions.first["id"] as Int // ID of a non deprecated the security question
let userSecurityAnswer = "1990" // Answer to the security question

client.registerSubjectWithEmail(userEmail, password: userPassword, securityQuestionID: userSecurityQuestionID, securityQuestionAnswer: securityQuestionLabel.text) { (err) in
	if err == nil {
		print("Successfully account created")
	}
}

Requirements:

Email to have the following:

  • Any valid and unique email
  • Use the ID of a security question that is not deprecated

Password to have the following

  • At least 8 characters long
  • At least one upper-case letter
  • At least one lower-case letter
  • At least one numeric digit

Using the Datastore

You can store and retrieve persistent data using the Datastore class.

let datastore = MDDatastoreFactory.create()
let user = datastore.userWithID(Int64(self.userID))

Because all MDObject instances (MDSubject, MDForm, etc..) are tied to a specific datastore, you must be careful to keep a datastore instance alive when using objects returned from its queries. Consider the following pseudocode:

func uploadData() {
  let myDatastore = MDDatastoreFactory.create()
  let subject = myDatastore.subjectWithID(1)

  subject.collectData(...) {
    subject.doSomething()
  }
}

Because collectData is asynchronous, it is possible that the datastore is released before doSomething() is called. To avoid this, set the datastore to nil at the end of the block in which the object is used. This keeps a reference around until it is no longer needed. Here's the revised uploadData() function:

func uploadData() {
  var myDatastore = MDDatastoreFactory.create()
  let subject = myDatastore.subjectWithID(1)

  subject.collectData(...) {
    subject.doSomething()
    myDatastore = nil
  }
}

Important Considerations:

  • Although there can be multiple Datastore instances, they all communicate with the same persistent store (a local SQlite database).
  • Datastore instances are not thread-safe. If you are creating a new thread - perhaps to make a network request asynchronously - then you should create a new Datastore to accompany it.
  • Instances loaded from a Datastore are not thread-safe. Instead of passing an instance to a separate thread, pass the instance's ID - for example, Java: user.getID(), Swift: user.objectID - and use a separate Datastore to load the instance.

Collecting and Uploading Arbitrary Data

You can collect arbitrary data (images, videos, accelerometer data, etc..) and have the results uploaded to AWS S3. This data will be automatically uploaded when the device has an internet connection.

// In CaptureImageViewController.swift
let img = UIGraphicsGetImageFromCurrentImageContext()
let imageData = UIImageJPEGRepresentation(img, 0.5)

// Collecting the data from the image view
subject.collectData(self.data, withMetadata: "Random String", withContentType: "image/jpeg", completion: { (err: NSError!) -> Void in
	if err == nil {
    	print("Successfully collected")
	}
}                   	

Network Requests

Babbage talks to back-end services to retrieve all information, such as users, subjects, forms, and so on. A normal application flow goes something like:

  1. Log in using a username / password
  2. Load subjects for the logged in user
  3. Load forms and present them to the user

The following code replicates this process:

client.logIn(username, inDatastore: datastore, password: password) 
{ (user: MDUser!, error: NSError!) -> Void in
  client.loadSubjectsForUser(user) 
  { (subjects: [AnyObject]!, error: NSError!) -> Void in
    client.loadFormsForSubject(subjects.first!) 
    { (forms: [AnyObject]!, error: NSError!) -> Void in
      ...
    }
  }
}

Important Considerations:

  • The preceding example assumes the user is associated with a single subject. In reality they may have multiple subjects associated with them.
  • The example assumes a best-case scenario where each request is successful. A robust application should have adequate error handling throughout the process.
  • To avoid interfering with the UI, make all requests asynchronously on a background thread.

Documentation

Refer to the AppConnect documentation for detailed instructions on how to use the various APIs.

About

This is a sample project in Swift that demonstrates how to build a complete application using the AppConnect SDK.

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published