Inspired by Google Forms
A simple yet powerful package for building dynamic questionnaires with branching, compact results, and clean list UIs.
- Compact, locale‑agnostic results (no question/answer text):
- Flat map:
{ "hear_about": "ig", "buy": ["rig","themes"] } - Branching tree:
[{"q":"hear_about","a":["ig"],"children":[...]}]
- Flat map:
- Stable IDs you control
Question.id(e.g."hear_about"). Fallback auto:q1,q2, …Question.answerChoiceIds(label → id). Fallback auto:a1,a2, …
- Ergonomic choice model:
Choice(id, label, [children])+ convenience factories:Question.single(...),Question.multi(...),Question.input(...),Question.textPage(...)
- List UIs for long labels
- Single choice → Radio list (opt‑in or via factories)
- Multi choice → Checkbox list (opt‑in or via factories)
- Zero breaking changes
- Your original
onNext(List<QuestionResult>)is intact. - New compact results are available via callbacks (no controller needed).
- Your original
Add to your app’s pubspec.yaml (use a local path if you work on the fork):
dependencies:
flutter_survey:
path: ../flutter_surveyIf you clone the repo or modify models, regenerate code:
dart run build_runner build --delete-conflicting-outputsclass Question {
final String question; // UI text
final bool singleChoice; // true=single, false=multi
final Map<String, List<Question>?> answerChoices; // label -> children?
final bool isMandatory;
final bool justText; // info page (no input)
final String? errorText;
final Map<String, dynamic>? properties; // UI opts
// New (optional; honored if set)
final String? id; // stable machine id
final Map<String, String>? answerChoiceIds; // label -> choice id
}// import 'package:flutter_survey/flutter_survey.dart';
const choices = [
Choice('ig', 'Instagram'),
Choice('tt', 'TikTok'),
Choice('ot', 'Other', [Question.input(question: 'Other:')]),
];
final q1 = Question.single(
id: 'hear_about',
question: 'How did you first hear about Randonautica?',
isMandatory: true,
choices: choices,
);
final q2 = Question.multi(
id: 'buy',
question: 'What are you most likely to purchase?',
choices: const [
Choice('rig','Unlock the RIG'),
Choice('themes','Unlock themes', [Question.input(question: 'Which themes?')]),
Choice('none','Nothing'),
],
);The factories set sensible UI defaults: single→radio list, multi→checkbox list. You can still override per question via
properties.
Pass your question list to Survey. You get both verbose (original) and compact results.
Survey(
initialData: <Question>[
Question.textPage(question: 'Welcome!'),
q1,
Question.input(id: 'intent', question: 'Share some intentions:'),
q2,
],
// Original verbose structure (still available)
onNext: (resultsTree) {
// final verbose = resultsTree.map((r) => r.toJson()).toList();
},
// New: compact results (no controller needed)
onCompactFlat: (flat) => debugPrint('compactFlat: $flat'),
// onCompactTree: (tree) => debugPrint('compactTree: $tree'),
);- Text Input → no
answerChoices(or useQuestion.input) - Single Choice →
singleChoice: true(orQuestion.single) - Multiple Choice →
singleChoice: false(orQuestion.multi) - Conditional / Nested → provide children under a label
- Informational page →
justText: true(orQuestion.textPage)
Question.single(
id: 'coffee_like',
question: 'Do you like coffee?',
choices: const [
Choice('yes','Yes', [
Question.multi(
id: 'brands',
question: "What brands have you tried?",
choices: const [Choice('nestle','Nestle'), Choice('sb','Starbucks')],
),
]),
Choice('no','No'),
],
);By default (via factories):
- Single choice → RadioList UI
- Multi choice → CheckboxList UI
You can override with properties per question:
Question(
question: 'Example',
singleChoice: true,
properties: {'useRadioList': false}, // fall back to SlidingButtonRow
answerChoices: const {'A': null, 'B': null},
);- Verbose (unchanged) →
onNext(List<QuestionResult>). - Compact Flat → recommended for storage/analytics:
- Keys:
Question.idif provided, else autoqN. - Values:
"choiceId"/["choiceId", ...]/ free text /null.
- Keys:
- Compact Tree → preserves branching:
[{"q":"hear_about","a":["ig"],"children":[{"q":"buy","a":["themes"]}]}]
IDs are your explicit IDs when set; otherwise deterministic qN/aN are used.
isMandatory: true→ shows error until an answer is provided (useerrorTextorSurvey.defaultErrorText).
- Your existing surveys and custom builders still work.
- No need to change persistence if you’re happy with verbose JSON.
- New compact callbacks are additive; use them when you want smaller, locale‑agnostic payloads.
Bugs, feature ideas, and PRs are welcome!
Michel — LinkedIn
