Skip to content
CARP Docs
Ask AISystemLightDark
On this page

Results

The result classes of a task run, which step records which result, and how to read the answers.

Copy MarkdownOpen inOpen in GitHubView as MarkdownOpen in Scira AIOpen in ChatGPTOpen in ClaudeOpen in Cursor

Every run of a task produces one RPTaskResult. Its results map holds one result per step that records something, keyed by the step identifier. RPUITask passes the result to onSubmit after the last step, and the partial result to onCancel when the participant leaves the task.

Result classes

All result classes extend RPResult, which has an identifier, a startDate and an endDate:

ClassRecorded byOther fields
RPTaskResultRPUITask, once per run, with the task identifierresults
RPStepResultQuestion and form stepsquestionTitle, results and answerFormat
RPActivityResultActivity stepsresults, stepTimes and interactions
RPConsentSignatureResultConsent review stepsconsentDocument, signature and userId
RPPermissionResultVisual consent steps that ask for permissionsstatuses

RPNoResult exists for JSON, but no step records it. For the details of the other results, see Read the activity result, Read the consent result and Read the permission statuses.

Results by step type

StepResultRecorded
RPInstructionStep, RPTimerStep, RPCompletionStepNone—
RPQuestionStepRPStepResult under the step identifierEach time the answer changes, and with a null answer when the participant skips the question.
RPFormStepRPStepResult under the form identifier, with one RPStepResult per question insideEach time an answer changes, and when the form is skipped or its countdown ends.
RPActivityStepRPActivityResult under the step identifierEach time the test reports a result.
RPVisualConsentStepRPPermissionResult under the step identifierWhen the participant leaves the last section, if the step asked for at least one permission.
RPConsentReviewStepRPConsentSignatureResult under the identifier of the review stepAfter AGREE, or after the signature when the document asks for one.

A later result for the same identifier replaces the earlier one, so the map holds the last answer to each question. In an RPNavigableOrderedTask, a result also stays in the map when the participant goes back and takes another branch. The map may then hold answers from steps that are no longer on the path the participant took.

Step results

An RPStepResult has these fields:

FieldTypeDescription
identifierStringThe identifier of the step.
questionTitleStringThe title of the question as written in the task, also when it is a translation key. The result of a form has the fixed text Form Step - See titles for every question included.
resultsMap<String, dynamic>The answer, under the key answer, which is also the constant RPStepResult.DEFAULT_KEY. The result of a form holds one RPStepResult per question instead, under the question identifier.
answerFormatRPAnswerFormatThe answer format of the question, with the options and limits that give the answer its meaning.
startDateDateTime?When the step appeared.
endDateDateTime?When the answer last changed.

A question inside a form that the participant never answered has an empty results map, without an answer key.

Answer values

The type of an answer depends on the answer format:

Answer formatType in DartIn JSON
RPChoiceAnswerFormatA List of RPChoice, also for a single choice[{"__type": "RPChoice", "text": "Yes", "value": 1, "isFreeText": false}]
RPIntegerAnswerFormatString"72"
RPDoubleAnswerFormatString"37.5"
RPSliderAnswerFormatdouble8.0
RPImageChoiceAnswerFormatRPImageChoice{"__type": "RPImageChoice", "imageUrl": "assets/images/happy.png", "value": 1, "description": "Happy"}
RPDateTimeAnswerFormatString from DateTime.toString()"2026-10-27 07:30:00.000"
RPTextAnswerFormatString"Slept badly after a late dinner."
Skipped or not answerednullnull

For what each format shows and stores, see Answer formats.

Answers in code

Look up a step result with getStepResultForIdentifier(), then read its answer. This function reads the answers of the quickstart survey and of a form like the form step example:

void printAnswers(RPTaskResult result) {
  // A choice answer is a list, also for a single choice
  final sleep = result.getStepResultForIdentifier('sleepQuality');
  if (sleep is RPStepResult) {
    final answer = sleep.results[RPStepResult.DEFAULT_KEY];
    if (answer is List) {
      final values = answer.whereType<RPChoice>().map((choice) => choice.value);
      print('Sleep quality: ${values.join(', ')}');
    }
  }

  // An integer answer is the typed text
  final coffee = result.getStepResultForIdentifier('coffeeCups');
  if (coffee is RPStepResult) {
    final text = coffee.results[RPStepResult.DEFAULT_KEY] as String?;
    print('Cups of coffee: ${int.tryParse(text ?? '')}');
  }

  // A form holds one step result per question
  final aboutYou = result.getStepResultForIdentifier('aboutYou');
  if (aboutYou is RPStepResult) {
    final age = aboutYou.results['age'];
    if (age is RPStepResult) {
      print('Age: ${age.results[RPStepResult.DEFAULT_KEY]}');
    }
  }
}

Test a choice answer with is List, not is List<RPChoice>. When the app registers RPLocalizations, a choice answer in the result that onSubmit receives is a List<dynamic> that holds RPChoice objects.

Timestamps

ResultstartDateendDate
RPTaskResultWhen RPUITask appearedWhen the last step finished; not set in the partial result of onCancel
RPStepResultWhen the step appearedWhen the answer last changed, not when the participant tapped NEXT
RPActivityResultNot setWhen the test last reported a result
RPConsentSignatureResultNot setWhen the participant agreed or signed, also available as signingDate
RPPermissionResultWhen the visual consent step appearedWhen the participant left the last section

All dates are local times of the device. JSON stores them as ISO 8601 strings without a time zone offset, such as 2026-10-27T08:15:02.118346. To compare results from several time zones, store the time zone of the device with each result.

Known issue

With RPLocalizations in the app, the question and form results that onSubmit receives carry the time of submission as their startDate and endDate. The task dates, the questions inside a form and the partial result of onCancel keep their real times. To keep the real step times, read the untranslated result from blocTask.lastTaskResult inside onSubmit.

Translated results

When the app registers RPLocalizations, onSubmit receives a copy of the result with texts in the language the participant saw:

  • The answer formats of question results get translated choice texts, choice detail texts, image descriptions, integer suffixes, and slider prefixes and suffixes. The suffix of a decimal number stays as written.
  • Choice answers are translated. An image choice answer keeps its description as written.
  • questionTitle keeps the title as written in the task, so a translation key stays a key.
  • The answers inside a form result are not translated.
  • Activity, consent and permission results pass through unchanged.

onCancel receives the result without translation. For translation keys and files, see Localization.

JSON format

Every result class serializes with jsonEncode() from dart:convert, and the JSON holds the class name of each result, answer format and choice in __type. It leaves out fields that are null, except a null answer. For the full result of a survey, see Quickstart.

The result of a form nests a step result per question. For the form step example with an age and no occupation, the result is:

{
  "__type": "RPStepResult",
  "identifier": "aboutYou",
  "startDate": "2026-10-27T09:02:11.540872",
  "endDate": "2026-10-27T09:02:11.540881",
  "questionTitle": "Form Step - See titles for every question included",
  "results": {
    "age": {
      "__type": "RPStepResult",
      "identifier": "age",
      "startDate": "2026-10-27T09:01:52.117374",
      "endDate": "2026-10-27T09:01:58.902511",
      "questionTitle": "How old are you?",
      "results": { "answer": "42" },
      "answerFormat": {
        "__type": "RPIntegerAnswerFormat",
        "minValue": 18,
        "maxValue": 110,
        "suffix": "years",
        "questionType": "Integer"
      }
    },
    "occupation": {
      "__type": "RPStepResult",
      "identifier": "occupation",
      "startDate": "2026-10-27T09:01:52.117391",
      "questionTitle": "What is your occupation?",
      "results": {},
      "answerFormat": {
        "__type": "RPTextAnswerFormat",
        "hintText": "For example, nurse",
        "autoFocus": false,
        "disableHelpers": false,
        "questionType": "Text"
      }
    }
  },
  "answerFormat": { "__type": "RPFormAnswerFormat", "questionType": "Form" }
}

RPTaskResult.fromJson() reads a result back after ResearchPackage.ensureInitialized(). The answers then stay plain JSON values. A choice answer is a list of maps instead of RPChoice objects, and the questions in a form result are maps too.

For more on JSON, see JSON surveys.

Quick links

Page · DocumentationCARP DocumentationPage · DocumentationIntroductionPage · DocumentationQuickstartPage · DocumentationTerminologyPage · DocumentationArchitecture