Build a research and care app, part 2: Schedule tasks
Learn how ResearchKit and CareKit can work together to take the tedium out of paper surveys. Continue coding along with us and explore how you can make it easier than ever to schedule surveys for your study participants. You'll also learn advanced techniques for crafting evolving regiments in CareKit and see how ResearchKit's active tasks can help capture important measurements out of clinic.
This is the second session in a three-part Code-Along series. To get the most out of this session, we recommend first watching “Build a research and care app, part 1.” And for more background on these frameworks, watch "ResearchKit and CareKit Reimagined” from WWDC19.
♪ Bass music playing ♪ ♪ Erik Hornberger: Welcome back to the research and care app code-along! This is part two of our three-part series where you and I will build out a physical therapy app for our friend Jamie. If you'd like to follow along, you can find our project in the session resources. I'll just go ahead and get logged in here. If you joined us for session one, you'll recall that we just finished onboarding and consent. In this session -- Oh, hang on. It looks like we've got a message from Jamie. "Got some new ideas for the app." "Did you see my last text?" Additional app requirements. Jamie has invited you to collaborate on a Notes document. OK, well, let's just see what we're going to be doing this time! Displaying forms, persisting some data, dynamic schedules, range of motion. OK. So it looks like Jamie wants us to set up a daily check-in survey where we ask the participants about how much sleep they got and how much pain they're experiencing. We'll show you how to use ResearchKit's form items to put multiple questions on a single page. Then, we'll need to parse the results from the ResearchKit survey and persist them into CareKit. That'll cause our completion rings to fill up and our task card UI to update. We'll also go into detail on how to create more advanced schedules with CareKit, and then we'll use one of those schedules -- along with ResearchKit -- to prompt participants to measure the range of motion in their knees. We've got a lot to cover here, so let's jump in! We'll get started with the daily check-in survey. Just like we did for our onboarding task, we'll begin by defining a schedule and a CareKit task. It's supposed to be a daily check-in, so let's just schedule it to happen each morning at, say, 8:00 am. We'll create the check-in task with a unique identifier and the schedule we just defined. And of course, we'll need to persist it here in our store. This is all exactly the same as what we did for the onboarding task. Hopefully it's starting to feel familiar to you! And just like the onboarding task, our next step will be to jump over to CareFeedViewController and tell CareKit how to display this task. This time around, let's make our solution a bit more generic. We'll fetch all the tasks for the current date. Then for each task, we'll create a view controller, and append that view controller into our list. This should scale a bit more nicely as we add more tasks. Now, we haven't actually defined this fetchTasks method or this viewController method. We'll need to drop down a bit further in our file and write them out. So here's our fetchTasks method. We start by creating a task query for a specific date, and we specify that we want to exclude tasks that don't have any scheduled events. This comes into play when we have tasks that don't happen every day. Let's say, for example, you've been given a prescription to take a medication every Monday. On Tuesdays and Wednesdays, that prescription is still assigned to you, there just aren't any pills to take. This property ensures that such tasks are not returned from the query. Once the query comes back, we'll pass the tasks we fetched to our completion handler. We'll also need to fill out a method that takes a task and returns a view controller. We'll inspect the id of the task, and if it's the checkIn task, we'll use the new SurveyTaskViewController we introduced in part one. Just like before, we'll supply a task, an eventQuery, and a reference to the storeManager. We'll also need to pass in a ResearchKit survey and a function for converting the ResearchKit result into an array of CareKit outcome values. We'll write these two methods in just a moment, but first let's take a look at how our app, ResearchKit, and CareKit are going to work together. Our app will create a ResearchKit survey. ResearchKit will take over and guide the participant through the survey flow. It will produce an ORKTaskResult, which it will hand back to our app. Then our app will convert the ResearchKit result into CareKit outcome values to be persisted in CareKit's store. Saving a new outcome will cause our completion rings to fill up and our task cards' UIs to update. All right, so let's tackle these two methods. We'll define them over in Surveys.swift. I've prepared a couple of identifiers that we'll be using down below. We need one method for creating a ResearchKit survey and one for converting the result into CareKit outcome values. We want to discover if there is a link between the amount of sleep a participant gets and how much pain they experience, so we'll create a form with two questions. The formStep requires a unique identifier, a title, and some text. We'll also mark it nonoptional, since we don't want the form to be skippable. Next, let's create our two items. The first will inquire about pain, and we'll make it a nonoptional question. What that means is that the form itself cannot be skipped, nor can we submit the form without an answer to this particular question. You can see that we also need to provide an answer format. The answer format tells ResearchKit what kind of answer to expect and how to prompt the user to enter it. Are they entering a weight, are they selecting an image, or are they recording their voice? In this case, we'll opt to use the ORKScaleAnswerFormat, which creates a UISlider. Sliders are a familiar interface to iOS users; they're intuitive, and they provide a much nicer experience than filling in circles on a paper survey. We'll specify a maximum pain score of 10, a minimum value of 1, set the step size to 1 so that only round numbers are allowed, and provide descriptions of the min and the max values. All right, let's drop down a little further and create one more item: a sleepItem. This is almost exactly the same as the painItem, and we'll use a nearly identical answer format as well. The only difference is the identifier, and that we've changed the min to 0 hours of sleep and the max to 12 hours of sleep. Now we just need to pass these two items into our form item, then create an ORKOrderedTask with a single formStep. The second function is supposed to take a ResearchKit task result and create an array of CareKit values to persist. But before we write this code, let's take a look at the structure of a ResearchKit task result so that we have a better idea of how to parse it. It's important to understand that ORKTaskResults are nested types. We want to start at the root result for the check-in survey and then drill down to the checkin.form. The checkin.form has two children itself, and we'll need to dig down to each of them. First, we want to find the answers given for the pain item identifier and then for the sleep item identifier. We know those are both scale question results, and we can get the scale answers off of them. In this example, 4 and 11. So that's what it looks like visually, and this is what the same process looks like in code. Starting at the root result, we find the first child that is marked with the form identifier. That result will have its own children, and we can extract all of the children with the type ORKScaleQuestionResult, of which we know there will be two. The pain answer is the first one marked with the pain identifier, and the sleep answer is the first one marked with the sleep identifier. Both will have scaleAnswer properties. Once we have the answers in hand, we need to convert them into a CareKit outcome value -- one for sleep and one for pain. The kind property here is optional. You don't need to set it, but it can be helpful if you want to look up a value later. We'll see why this is useful in part three! The last thing that we need to do is return our two values from this function. With all that in place, our daily check-in should be good to go! Let's run the app and see how we're doing. Note that we have already completed onboarding in part one, so we don't need to go through the consent flow again. This is the true content of our app, and the CareKit check-in task we've just created is right where we expect it to be. Tapping on the card takes us into the ResearchKit survey. We'll just go ahead and give some answers here. Let's go with, like, say, a 4 for pain and we'll go with 8 hours of sleep. When we get back to our Care Feed, we can see that the completion ring at the top fills up, indicating that the answers from ResearchKit were successfully parsed into CareKit. This is looking really good. So we've finished our check-in survey with its multiquestion form, and we've verified that data is persisted into CareKit the way that we want it to be. That means we're about halfway there! It's time to move on to advanced schedules in CareKit, which we'll later apply to our range of motion task. Like the check-in task and the onboarding task before it, the first step is to define a schedule. But we're going to do something a bit more involved for this one. Jamie has asked that we reduce how often we ask participants to measure their range of motion as time goes on. Specifically, we want to set up a schedule that prompts the participant to measure their range of motion every day for the first week, but then, only once a week until the end of the month, and then never again after that. So once a day for the first week, then once a week for the rest of the month. Let's begin by defining a few key dates: thisMorning, nextWeek, and nextMonth. Now, if you want to create more nuanced schedules in CareKit, OCKScheduleElement is the tool for the job. Schedule elements have a start date, an end date, and repeat at some interval during that period. The dailyElement here starts this morning, ends next week, and repeats every day. The second element is the weeklyElement. It starts next week, ends next month, and repeats every week. Now that we have our two elements, we can compose them together to create a compound schedule, and then we can create a new range of motion task using that schedule. Of course, we'll need to add it into our store as well. Just like our other tasks, the next step is to hop back into our CareFeed and specify how we want CareKit to display this task. Once again, we'll use the SurveyTaskViewController, and we'll need to supply a survey, as well as functions, to extract the answers. Let's jump back into Surveys.swift and stub those out to provide a bit of structure before we get started. OK, so the range of motion task is actually pretty simple. As a matter of fact, it's built into ResearchKit as a predefined task. We just need to give it an identifier and specify which knee is to be measured. Now the predefined task has a completion step built into it, and the message it shows is a reasonable default, but we'd really like to show a custom message tailored to our use case. So we're going to tell ResearchKit to omit the standard completion step. Then, we'll define our own and give it an encouraging message specifically related to physical therapy. So that's the first function. The second one is where we'll convert from a ResearchKit result to CareKit outcome values. Hopefully this is starting to feel familiar now. There are a lot of ways we can go about this, but today we're going to dig down until we get to the first range of motion result. And we know there will only be one. Range of motion results actually have a bunch of useful properties on them, but the one we're most interested in for our use case is range, which measures how far the participant was able to bend their knee in degrees. We can use the kind field along with key paths to make it easy to look up this value later. That's foreshadowing for part three, by the way. All right, and that wraps up our function for extracting the outcome values. Nice! I think we're all set up. Let's run our app and I'll show you how it all works. The first thing that we want to check is that our schedule is working the way that we intend it to. The Range Of Motion task should show up every day during the first week. As I page through the 8th, and the 9th, and the 10th, the range of motion task shows up every day. But when I get out to next week, it stops showing up except on Monday here, which is the one day of the week that we still want it to appear. Here it's still showing up only once per week. But if we go out even further into the future, all the way to next month, it doesn't show up at all anymore. CareKit schedules are a great way to preprogram regimens like this. Let's jump back to today and try this out.
When I tap Begin, our instructions step helps me understand what to do. When I start, Siri will give me audio guidance, so I'm just going to follow her instructions. Siri: Place your device on your left knee. Tap the screen and extend your left knee as far as you can. Touch anywhere to continue. When you are done, return your left knee to the start position, then tap anywhere. Erik: Nice. That's pretty neat, right? Usually you'd have to make a visit to the doctor's office and a therapist would help you take measurements like this, but ResearchKit can help participants take measurements in their own homes. I think Jamie will be really impressed with the progress we've made. I'll FaceTime him right after this and send him the latest version of our app on TestFlight. While I'm bringing Jamie up to speed on our progress, you can find more info about our frameworks on researchandcare.org, or read through the source code on GitHub. In session one, we set up onboarding. In this session, we scheduled a pair of tasks. Join us again for our third and final session to see how ResearchKit and CareKit can help us finish up our app. See you soon! ♪
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.