Skip to content
CARP Docs
Ask AISystemLightDark
On this page

Quickstart

Build a short survey, show it with RPUITask and print the participant's answers as JSON.

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

Build a Flutter app that shows a two-question daily survey and prints the answers as JSON when the participant finishes it.

Prerequisites

  • A Flutter app set up as described in Installation, including the translation file assets/lang/en.json.

Complete example

Replace the contents of lib/main.dart with:

lib/main.dart
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:research_package/research_package.dart';

void main() {
  // 1. Initialize Research Package
  ResearchPackage.ensureInitialized();

  runApp(const SurveyApp());
}

class SurveyApp extends StatelessWidget {
  const SurveyApp({super.key});

  @override
  Widget build(BuildContext context) => MaterialApp(
    // 2. Load the texts of Research Package and of the app
    localizationsDelegates: [
      RPLocalizations.delegate,
      GlobalMaterialLocalizations.delegate,
      GlobalCupertinoLocalizations.delegate,
      GlobalWidgetsLocalizations.delegate,
    ],
    supportedLocales: const [Locale('en')],
    home: const HomePage(),
  );
}

// 3. Define the survey
RPOrderedTask buildSurvey() => RPOrderedTask( 
  identifier: 'dailyCheckIn',
  steps: [
    RPInstructionStep(
      identifier: 'intro',
      title: 'Daily check-in',
      text: 'Two short questions about the last day.',
    ),
    RPQuestionStep(
      identifier: 'sleepQuality',
      title: 'How well did you sleep last night?',
      answerFormat: RPChoiceAnswerFormat(
        answerStyle: RPChoiceAnswerStyle.SingleChoice,
        choices: [
          RPChoice(text: 'Very well', value: 3),
          RPChoice(text: 'Fairly well', value: 2),
          RPChoice(text: 'Poorly', value: 1),
          RPChoice(text: 'Very poorly', value: 0),
        ],
      ),
    ),
    RPQuestionStep(
      identifier: 'coffeeCups',
      title: 'How many cups of coffee did you drink yesterday?',
      answerFormat: RPIntegerAnswerFormat(
        minValue: 0,
        maxValue: 20,
        suffix: 'cups',
      ),
    ),
    RPCompletionStep(
      identifier: 'done',
      title: 'Thank you',
      text: 'Your answers are saved.',
    ),
  ],
);

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) => Scaffold(
    body: Center(
      child: FilledButton(
        // 4. Show a new survey on a new route
        onPressed: () {
          final task = buildSurvey();
          Navigator.of(context).push(
            MaterialPageRoute<void>(builder: (_) => SurveyPage(task: task)),
          );
        },
        child: const Text('Start survey'),
      ),
    ),
  );
}

class SurveyPage extends StatelessWidget {
  const SurveyPage({super.key, required this.task});

  final RPOrderedTask task;

  @override
  Widget build(BuildContext context) => RPUITask( 
    task: task,
    // 5. Print the result as JSON
    onSubmit: (RPTaskResult result) => print(jsonEncode(result)),
    onCancel: (RPTaskResult? result) => print('Survey left before the end'),
  );
}

How it works

StageWhat it does
1. InitializeResearchPackage.ensureInitialized() registers the model classes, which fromJson() needs to load tasks and results from JSON. This app builds its survey in Dart, so the call only prepares for later JSON use.
2. TextsRPLocalizations.delegate loads the package's own texts, such as NEXT and DONE, and your translations from assets/lang/en.json. The GlobalMaterialLocalizations, GlobalCupertinoLocalizations and GlobalWidgetsLocalizations delegates add Flutter's own texts for each language you add to supportedLocales.
3. SurveyAn RPOrderedTask shows its steps in order: an instruction, two questions and a completion screen. Each question has an answer format that decides how the participant answers.
4. RouteEach tap builds a new task and shows it on a route of its own. A new task per run matters, because some objects in a task change while it runs. RPUITask closes the route by itself after the last step and when the participant leaves the survey.
5. ResultonSubmit receives one RPTaskResult with a result per question, keyed by the step identifier. onCancel runs instead when the participant leaves the survey early.

Run the app

Run the app with flutter run and tap Start survey. The top bar shows the position, such as "1 of 4" on the first screen. NEXT is enabled once the question on screen has an answer.

After DONE on the last screen, the survey closes and the console shows the result, formatted here for reading:

{
  "__type": "RPTaskResult",
  "identifier": "dailyCheckIn",
  "startDate": "2026-10-27T08:14:41.207415",
  "endDate": "2026-10-27T08:15:02.118346",
  "results": {
    "sleepQuality": {
      "__type": "RPStepResult",
      "identifier": "sleepQuality",
      "startDate": "2026-10-27T08:15:02.119352",
      "endDate": "2026-10-27T08:15:02.119571",
      "questionTitle": "How well did you sleep last night?",
      "results": {
        "answer": [
          { "__type": "RPChoice", "text": "Fairly well", "value": 2, "isFreeText": false }
        ]
      },
      "answerFormat": {
        "__type": "RPChoiceAnswerFormat",
        "questionType": "SingleChoice",
        "choices": [
          { "__type": "RPChoice", "text": "Very well", "value": 3, "isFreeText": false },
          { "__type": "RPChoice", "text": "Fairly well", "value": 2, "isFreeText": false },
          { "__type": "RPChoice", "text": "Poorly", "value": 1, "isFreeText": false },
          { "__type": "RPChoice", "text": "Very poorly", "value": 0, "isFreeText": false }
        ],
        "answerStyle": "SingleChoice"
      }
    },
    "coffeeCups": {
      "__type": "RPStepResult",
      "identifier": "coffeeCups",
      "startDate": "2026-10-27T08:15:02.119587",
      "endDate": "2026-10-27T08:15:02.119591",
      "questionTitle": "How many cups of coffee did you drink yesterday?",
      "results": { "answer": "2" },
      "answerFormat": {
        "__type": "RPIntegerAnswerFormat",
        "minValue": 0,
        "maxValue": 20,
        "suffix": "cups",
        "questionType": "Integer"
      }
    }
  }
}

The instruction and completion steps record nothing. The choice question stores a list of the selected choices, and the integer question stores the typed text as a string.

Because the app registers RPLocalizations, both step results carry the time of submission as their dates, not the time of the answer. See Timestamps.

Troubleshooting

The app shows a blank screen at startup

Add the file assets/lang/en.json, declare assets/lang/ under flutter: assets in pubspec.yaml, and restart the app. RPLocalizations.delegate fails to load without the file, and Flutter does not show the first screen until every delegate has loaded.

NEXT stays disabled

Give the question on screen a valid answer. An answer is missing or invalid when:

  • the integer field holds anything other than a whole number from minValue to maxValue;
  • in a single choice, the participant taps the selected option again, which clears the selection;
  • the participant has not moved a slider or turned a date picker yet.

Next steps

Tasks and steps

Task classes and every step type, with their parameters.

Answer formats

The seven answer formats and the value each one stores.

Results

Result classes, answer values, timestamps and the JSON format.

Running tasks

Show a task, handle its callbacks and customize its bars.

Quick links

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