CareKit Tutorial for iOS: Part 1

In the first part of our CareKit Tutorial for iOS, you’ll learn the basics of using CareKit by building a Zombie Training and Symptom Tracker. By Jeff Rames.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Assessment Activities

As you might have guessed, it’s time to eat some brai … Rather, it’s time to add some assessment activities to the store!

Open CarePlanData.swift and head over to init(carePlanStore:). Replace the line //TODO: Define assessment activities with the following:

let pulseActivity = OCKCarePlanActivity
  .assessment(withIdentifier: ActivityIdentifier.pulse.rawValue,
                            groupIdentifier: nil,
                            title: "Pulse",
                            text: "Do you have one?",
                            tintColor: UIColor.darkGreen(),
                            resultResettable: true,
                            schedule: CarePlanData.dailyScheduleRepeating(occurencesPerDay: 1),
                            userInfo: ["ORKTask": AssessmentTaskFactory.makePulseAssessmentTask()])

let temperatureActivity = OCKCarePlanActivity
  .assessment(withIdentifier: ActivityIdentifier.temperature.rawValue,
                            groupIdentifier: nil,
                            title: "Temperature",
                            text: "Oral",
                            tintColor: UIColor.darkYellow(),
                            resultResettable: true,
                            schedule: CarePlanData.dailyScheduleRepeating(occurencesPerDay: 1),
                            userInfo: ["ORKTask": AssessmentTaskFactory.makeTemperatureAssessmentTask()])

You’ve defined two OCKCarePlanActivity objects—one for pulse and one for temperature—using a convenience initializer that creates assessment activities. This required a couple parameters not used in the intervention activities created earlier:

  1. resultResettable takes a Boolean that indicates whether the user can re-take the assessment. When turned off, once data is entered, there’s no covering it up if you’re a zombie.
  2. You place a dictionary in userInfo with the key "ORKTask". The value is a ResearchKit task (ORKTask) created by calling factory methods provided in AssessmentTaskFactory, a struct included with the starter project. CareKit can leverage ResearchKit tasks for obtaining health data, and you’ll see shortly how these tasks are used when the user completes an assessment.

At the bottom of init(carePlanStore:), add your new tasks to the array you loop through when calling addActivity(). It should now look like this:

for activity in [cardioActivity, limberUpActivity, targetPracticeActivity,
                 pulseActivity, temperatureActivity] {
                  add(activity: activity)
}

Build and run, and your new activities will appear in the Symptom Tracker tab. You’re now at 0% progress, because you haven’t completed either assessment yet. Note that as long as it isn’t the first day of the week, you can select and view prior days. Future days are never selectable.
CareKit tutorial

There’s no putting it off—you might as well check to see if you’ve become a zombie. Select one of the assessments and … nothing happens!

CareKit tutorial

Handling Input

On the Care Card, intervention activities have a binary input—the user simply taps an event on or off to indicate if it occurred. Assessments are more versatile; they can capture all types of data and thus require a variety of user interfaces. For this reason, you’re required to provide a view controller to handle assessment input.

ResearchKit provides a number of options for handling input of health data, and CareKit is designed to work well with them. As you may recall from the ResearchKit with Swift tutorial, input is gathered with ORKTaskViewController objects which require ORKTask objects. You’ve already bundled a task in the userInfo of your assessment object—now it’s time to use it.

Open TabBarViewController.swift and add the following to the bottom of the file:

// MARK: - OCKSymptomTrackerViewControllerDelegate
extension TabBarViewController: OCKSymptomTrackerViewControllerDelegate {
  func symptomTrackerViewController(_ viewController: OCKSymptomTrackerViewController,
                                    didSelectRowWithAssessmentEvent assessmentEvent: OCKCarePlanEvent) {
    guard let userInfo = assessmentEvent.activity.userInfo,
      let task: ORKTask = userInfo["ORKTask"] as? ORKTask else { return }
    
    let taskViewController = ORKTaskViewController(task: task, taskRun: nil)
    //TODO: Set a delegate
    
    present(taskViewController, animated: true, completion: nil)
  }
}

The Symptom and Measurement Tracker view controller requires an OCKSymptomTrackerViewControllerDelegate that must implement symptomTrackerViewController(_:didSelectRowWithAssessmentEvent:). This method is required to present a view controller for obtaining assessment data. It unwraps the ORKTask you stored in userInfo and uses that to initialize an ORKTaskViewController, then present it.

Hop back up to createSymptomTrackerStack() and add the following just after viewController is allocated:

viewController.delegate = self

This sets the TabBarViewController as the delegate, which means the extension you just defined will be used when an assessment is selected.

Build and run, then head to the Symptom Tracker tab. Select either Pulse or Temperature and…you’ll get a crash. If you look in the console, there will be a message similar to this:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSHealthShareUsageDescription must be set in the app's Info.plist in order to request read authorization.'

This is because ResearchKit tasks rely on HealthKit, and you haven’t yet enabled that capability. You’ll get to that in a bit, but for now you need to add the plist entry to appease this check.

Open Info.plist within ZombieKit and add the NSHealthShareUsageDescription key with the value I want to obtain your pulse and temperature data because zombies:

Screen Shot 2016-09-24 at 1.19.09 PM

You’ll access HealthKit data later in this tutorial, at which point the user will be prompted for authorization including the description you just provided.

Build and run, and once again select Pulse or temperature on the Symptom Tracker tab. Your ResearchKit controller now presents data from the task factory.

Note: In the console, you might see a warning beginning with [ResearchKit][Warning]. This is because ResearchKit wants to check HealthKit for the data, and you haven’t set that up yet.

CareKit tutorial

If you try to hit Done or Cancel then End Task, nothing happens. This is because an ORKTaskViewController requires a delegate callback to handle its results. Back in TabBarViewController.swift, add the following at the bottom of the file:

// MARK: - ORKTaskViewControllerDelegate
extension TabBarViewController: ORKTaskViewControllerDelegate {
  func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith
    reason: ORKTaskViewControllerFinishReason, error: Error?) {
    // 1
    defer {
      dismiss(animated: true, completion: nil)
    }
    
    // 2
    guard reason == .completed else { return }
    guard let symptomTrackerViewController = symptomTrackerViewController,
      let event = symptomTrackerViewController.lastSelectedAssessmentEvent else { return }
    //TODO: convert ORKTaskResult to CareKit result and add to store
  }
}
  1. This dismisses the task view controller in a defer block that will execute after handling the results.
  2. Upon successful completion, you unwrap symptomTrackerViewController.lastSelectedAssessmentEvent to gain access to the event tied to the task that triggered the current task flow. In a moment, you’ll use this reference to save the results to the store.

You can’t simply save a ResearchKit task result into CareKit—you need to first convert it. Open CarePlanStoreManager.swift and import ResearchKit:

import ResearchKit

Then add the following method to CarePlanStoreManager:

func buildCarePlanResultFrom(taskResult: ORKTaskResult) -> OCKCarePlanEventResult {
  // 1
  guard let firstResult = taskResult.firstResult as? ORKStepResult,
    let stepResult = firstResult.results?.first else {
      fatalError("Unexepected task results")
  }
  
  // 2
  if let numericResult = stepResult as? ORKNumericQuestionResult,
    let answer = numericResult.numericAnswer {
    return OCKCarePlanEventResult(valueString: answer.stringValue, unitString: numericResult.unit, userInfo: nil)
  }
  
  // 3
  fatalError("Unexpected task result type")
}

This converts a ResearchKit result into a CareKit result.

  1. The ORKTaskResults you created have a single step, so you pull the first and only one into firstResult. Next you grab the first and only question result of this step, which will contain the data you collected.
  2. You cast the result as a ORKNumericQuestionResult, which is the only result type your tasks collect. You then grab the answer and pass it as the value when creating and returning an OCKCarePlanEventResult.
  3. Remember that you only call this method when a task successfully completes, so if it doesn’t contain a value, you’re simply failing.

Return to TabBarViewController.swift and replace //TODO: convert ORKTaskResult to CareKit result and add to store with:

let carePlanResult = carePlanStoreManager.buildCarePlanResultFrom(taskResult: taskViewController.result)
carePlanStoreManager.store.update(event, with: carePlanResult, state: .completed) {
  success, _, error in
  if !success {
    print(error?.localizedDescription)
  }
}

This passes your ResearchKit task result to buildCarePlanResultFrom(taskResult:) and gets back an OCKCarePlanEventResult. You pass that carePlanResult along with the current event to update(:_with:state:completion:) to commit it to the store. You print an error message if it was unsuccessful.

Finally, head back to symptomTrackerViewController(_:didSelectRowWithAssessmentEvent:) and replace //TODO: Set a delegate with:

taskViewController.delegate = self

Now when the user submits a completed ResearchKit task, your new delegate method will be invoked to convert and save it.

Build and run. Now when you complete an assessment, the task controller will dismiss, your care store will update, and the symptom tracker will update to reflect changes to the completion percent and recorded value.

CareKit tutorial

Note: You’ll likely want to employ local notifications to notify users when it’s time to complete an activity. To learn more about creating local notifications, check out the Local and Remote Notification Programming Guide from Apple.