Building a COVID-19 symptom tracker in CareKit: Part 1
Introduction
CareKit is a powerful OpenSource framework which provides a lot of functionality out of the box to help us build applications to help guide users through Care plans.
In order to get to grips with the CareKit framework and how to use it, we will build a simple application which allows a user to locally track some symptoms of COVID-19. This simple application is for demonstration purposes only and merely serves as a premise for us to explore the CareKit framework. Please use the official symptom tracker recommended for your country.
We will start off small, for this first tutorial the application we build will allow a user to track coughing episodes each day.
Integrating CareKit
In order to get started we our going to create a new Xcode project and select a SingleView application as our kicking off point.
In Xcode 11 integrating CareKit really couldn’t be simpler. CareKit itself is open source and available through Swift Package Manager. This means we can integrate the dependency directly through Xcode, by clicking File -> Swift Packages -> Add Package Dependency
. The url for the dependency is
https://github.com/carekit-apple/CareKit
At the time of writing it is recommended to point directly at the master branch.
For this tutorial we will only import the top level CareKit package. This package depends on CareKitUI and CareKitStore so we will still have access to everything we need to build a CareKit project.
Creating a daily view
While not a requirement, CareKit is definitely built around the OCKDailyPageViewController
. This class provides a lot for us, it creates a scrollable calendar with completion rings and a nice API for creating tasks for our user to complete.
To get started we will create a subclass of OCKDailyPageViewController
:
This subclass will serve as our application landing page. It is worth noting at this point that init(fromCoder:)
has not been implemented for OCKDailyPageViewController
and will crash the application if called. This is because OCKDailyPageViewController
must be initialised with a OCKSynchronizedStoreManager
, which itself must be initalised with a OCKAnyStoreProtocol
.
There is a lot going on here so lets break it down:
OCKAnyStoreProtocol
: A protocol describing a type which CareKit can use to read and write care plans and patients progress. We could write a custom implementation of this which connects the app to a remote server. For this tutorial we will use the built inOCKStore
which is anOCKAnyStoreProtocol
compliant wrapper aroundCoreData
.OCKSynchronizedStoreManager
: This object basically wraps theOCKAnyStoreProtocol
’s five separate delegates into a Combine friendly API (using publishers).OCKDailyPageViewController
: This is our main view controller. Behind the scene it uses Combine to help reflect changes across the application. For example completing a task will automatically update a users progress rings without us as developers having to write any code.
This can still be a lot to take in so it is definitely worth exploring the implementations of the above (which we can because CareKit is open source).
To make life easier we are going to set our project to NOT use a storyboard and instantiate our view controllers automatically. We can do this by deleting the storyboard (and the automatically generated ViewController.swift file) and removing the reference to it in our Info.plist (Application Scene Manifest > Scene Configuration > Application Session Role > Item 0 > Storyboard
).
To make life simpler for ourselves and to save us from having to query UIApplication.shared as? AppDelegate
in lots of places we will create a singleton object that wraps our instance of OCKSynchronizedStoreManager
.
While singletons are generally frowned upon, in this case the object only needs to exist once and for the entire lifecycle of the application. So for the purposes of this demo I feel comfortable in recommending something like:
Those familiar with setting up a CoreData
stack will recognise the method used here. We lazily create a store with a unique name (as our application could have more than one store). The main difference here is we wrap it in our OCKSynchronizedStoreManager
.
Now we can instantiate our SymptomTrackerViewController
and get something on screen! If we head to our SceneDelegate
and replace the autogenerated code with the following:
Note we added a little styling by adjusting the tint colour of the window. If we build and run our application we should see something like this:
The app, while still very plain is starting to take shape. We can navigate through different days, see our inactive completion rings (as we have no tasks) and navigate to the current day with the left bar button item.
Tracking coughing episodes
The first task we are going to create for our users is one that allows them to log any coughing episodes. According to nhs.uk any more than three coughing episodes in 24 hours is a potential symptom of COVID-19.
First things first we will add a TaskIdentifier
enum to our CareStoreReferenceManager
. This will contain a unique identifier for every kind of task we want a user to perform.
Next we will, for the purposes of our demo, create an extension on OCKStore
to populate it with our daily tasks.
The above code is actually rather simple in essence we create a schedule for our task, then we create a task passing in the schedule at which it should be performed and add the task to our store.
We now need to call populateDailyTasks()
when we create our store in our CareStoreReferenceManager
This will create our task but we still need to display it. Head over to SymptomTrackerViewController
and add the dailyPageViewController(_:prepare:for:)
function:
The above function is called when the daily page view controller loads a date. This happens on the initial page load and when you navigate to a new date on the calendar. When this happens we take the following steps:
- Create a query for the date to be displayed for every known type of task.
- Execute the query and handle the results
- For each task returned we check it’s identifier so we can decide how to handle it
- For our
coughingEpisodes
task we will display it using some built in CareKitUIOCKButtonLogTaskViewController
which creates a card like view - We append the card to the instance of
OCKListViewController
passed into the function.
Now if we build and run our application we should see our card displayed and we can interact with it.
One thing to note here is that logging a coughing episode affects the users completion rings. It will say a user has not completed their daily tasks if they do not log a coughing episode. While this is useful functionality that we will dive into in further tutorials it does not make sense for this use case. We can fix this by setting the impactsAdherence
property of our coughingEpisode
task to false. This means this task will be ignored for calculating whether or not a user has completed their daily tasks.
Summary
In this tutorial we have created a new project, integrated CareKit and built the skeleton of our CareKit application. We have a daily view, we have covered creating tasks and scheduling, as well as rendering a default component. We still have lots to cover though.
In the next part of this tutorial we will look at creating a custom card to allow the user to log their temperature every day.
If you would like to see the source code for this application as it stands at the end of this tutorial it is open sourced under this tag.