Advanced survey navigation with ResearchKit
Designing surveys with ResearchKit can be as simple or as complex as you need it to be.
Our previous article gave a general presentation about how to create a survey, now we want to focus on navigation. In particular, how to build non-linear surveys with ResearchKit, with custom and dynamic navigation rules.
The most simple solution to build a survey is to use
ORKOrderedTask (documentation), and setup a linear sequence of steps. All it needs is an array of
ORKStep when you initialize it.
let steps = [introStep, step1, step2, completionStep] let task = ORKOrderedTask(identifier: "...", steps: steps)
Navigable ordered task
To configure non-linear surveys, ResearchKit provides
As the name suggests, it’s actually a subclass of
ORKOrderedTask, so you initialize it with an array of steps. But this class also manages “step navigation rules” (
ORKStepNavigationRule, documentation). These rules describe how a particular step can lead to another particular step, and under which conditions.
You can add a new rule with
setNavigationRule:forTriggerStepIdentifier:. The “trigger” step identifier must refer to a step already in the
steps array. The rule will be evaluated after the trigger step, to figure out what the next step should be. There can only be one rule by trigger step identifier (otherwise the controller wouldn’t know which rule to apply), if you call this method a second time with the same step identifier, the new rule will simply overwrite the previous rule. You can remove a rule for a given step identifier with
removeNavigationRuleForTriggerStepIdentifier:. Existing rules can be fetched with
navigationRuleForTriggerStepIdentifier: (this method returns
nil if there’s no rule associated with that step). Finally, you can access all the existing rules with the
stepNavigationRules dictionary property (the trigger step identifiers are the keys, the rules are the associated values).
You don’t need to specify a rule for every step. The initial steps array acts as a fallback order. So if the controller doesn’t have a rule for a particular step, it simply presents the next item from the steps array.
The first type of rule is
ORKDirectStepNavigationRule (documentation). This is a literally straightforward, non-conditional navigation, taking a single parameter: the destination step identifier.
Here’s an example of survey with a direct step navigation rule. There are 3 steps: questionA, questionB, and questionC. Then we configure a direct navigation from questionA to questionC.
let questionA = ORKQuestionStep(identifier: "questionA") questionA.answerFormat = ORKBooleanAnswerFormat() let questionB = ORKQuestionStep(identifier: "questionB") questionB.answerFormat = ORKBooleanAnswerFormat() let questionC = ORKQuestionStep(identifier: "questionC") questionC.answerFormat = ORKBooleanAnswerFormat() let steps = [questionA, questionB, questionC] let task = ORKNavigableOrderedTask(identifier: "task", steps: steps) let rule = ORKDirectStepNavigationRule(destinationStepIdentifier: questionC.identifier) task.setNavigationRule(rule, forTriggerStepIdentifier: questionA.identifier)
When the participant completes questionA, the survey jumps to questionC. There’s no rule associated with questionC, and it’s the last item in the steps array, so the survey stops after completing this step, thus ignoring questionB.
This direct navigation might not be very useful on its own, but it’s frequently used in combination with other conditional rules, for instance to circle back to a particular question after a specific rule.
The second type of navigation rule is
You initialize it with one or multiple predicate(s), associated with destination step(s). These predicates can evaluate the results from the last step, or even a combination of results from multiple steps.
You need to use the generic
NSPredicate class, but
ORKResultPredicate (documentation) offers a wide range of convenience methods to easily build predicates based on ResearchKit results classes (“expected answer”, “minimum expected answer value”, and so on).
As an example, we can reuse the questions A, B, and C from the last sample code, and set a predicate step navigation rule instead of the direct navigation. We’ll test the result from questionA, and jump to questionC only if it’s positive. Otherwise, the default navigation principles apply, and the participant proceeds to questionB.
let predicate = ORKResultPredicate.predicateForBooleanQuestionResultWithResultSelector( ORKResultSelector(resultIdentifier: questionA.identifier), expectedAnswer: true) let rule = ORKPredicateStepNavigationRule( resultPredicatesAndDestinationStepIdentifiers: [(predicate, questionC.identifier)]) task.setNavigationRule(rule, forTriggerStepIdentifier: questionA.identifier)
(These detailed classes and methods names are difficult to read, but they’re fairly self-explanatory, and can express complex rules with just a few lines of code.)
Note that you can set an optional
defaultStepIdentifier for the navigation rule, to give a fallback destination in case of no matching predicate. You can also assign
additionalTaskResults to perform the predicate on results from another task. Several predicates can be combined using
NSCompoundPredicate, to express “and”/“or”/“not” logical statements.
Loops and backward navigation
These rules make it possible to jump back to a previous step, or even repeat a sequence of steps any number of times. Keep in mind that doing so simply overwrite previous results, “correcting” existing results.
If you’re trying to collect multiple results for the same question, you will need to actually duplicate these steps, and make sure that they all have a distinct identifier.
Navigable ordered task is extremely flexible, and should accommodate the vast majority of surveys structures. But if the various predicates don’t fit your needs, you can implement your own
ORKTask subclass (documentation).
The most important method that you need to provide is
stepAfterStep:withResult:. As you can guess, it should return the next step, based on the current step and its results (and any other custom value that you choose to manage with your class). You also need to implement
stepBeforeStep:withResult:, to let the user navigate backward (or return
nil, to prevent the participant from navigating back to a previous step).
Custom tasks require a significant amount of work, but they let you fully control the survey progress. ResearchKit is flexible enough to accommodate any type of question, with any type of navigation.