Skip to content

Commit 4f01927

Browse files
committed
fix: immutable surveystateprovider
1 parent 7e5b284 commit 4f01927

File tree

3 files changed

+86
-13
lines changed

3 files changed

+86
-13
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
# 1.0.3 (To be released)
1+
# 1.0.3
22

33
- CHORE: Removed depracted lint `package_api_docs`
4+
- FIX: Refactored SurveyStateProvider to follow proper InheritedWidget pattern - separated mutable state into StatefulWidget wrapper to prevent reinstantiation on rebuilds
5+
- FIX: Fixed SurveyStateProvider state persistence by using SurveyStateProviderWidget (StatefulWidget) with immutable SurveyStateProvider (InheritedWidget)
46

57
# 1.0.2
68

lib/src/presenter/survey_state_provider.dart

Lines changed: 82 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,18 @@ import 'package:collection/collection.dart';
44
import 'package:flutter/material.dart' hide Step;
55
import 'package:survey_kit/survey_kit.dart';
66

7-
/// StatefulWidget that manages the survey state
7+
/// A [StatefulWidget] that manages the mutable state for the survey.
8+
///
9+
/// This widget holds the survey's state, results, and handles all state transitions
10+
/// through the [onEvent] method. It creates an immutable [SurveyStateProvider]
11+
/// on each rebuild to expose the state to descendant widgets.
12+
///
13+
/// This follows the recommended Flutter pattern of separating mutable state
14+
/// (StatefulWidget) from the immutable state exposure (InheritedWidget).
815
class SurveyStateProviderWidget extends StatefulWidget {
16+
/// Creates a [SurveyStateProviderWidget].
17+
///
18+
/// All parameters are required except [stepShell].
919
const SurveyStateProviderWidget({
1020
super.key,
1121
required this.taskNavigator,
@@ -15,10 +25,19 @@ class SurveyStateProviderWidget extends StatefulWidget {
1525
this.stepShell,
1626
});
1727

28+
/// The navigator that manages the task flow and step transitions.
1829
final TaskNavigator taskNavigator;
19-
final Function(SurveyResult) onResult;
30+
31+
/// Callback invoked when the survey is completed or closed.
32+
final void Function(SurveyResult) onResult;
33+
34+
/// Optional custom shell widget builder for wrapping steps.
2035
final StepShell? stepShell;
36+
37+
/// Global key for managing the Navigator state.
2138
final GlobalKey<NavigatorState> navigatorKey;
39+
40+
/// The widget below this widget in the tree.
2241
final Widget child;
2342

2443
@override
@@ -54,6 +73,13 @@ class _SurveyStateProviderWidgetState extends State<SurveyStateProviderWidget> {
5473
_surveyStateStream.add(_state);
5574
}
5675

76+
/// Handles survey events and triggers appropriate state transitions.
77+
///
78+
/// Supported events:
79+
/// - [StartSurvey]: Initializes the survey with the first step
80+
/// - [NextStep]: Advances to the next step
81+
/// - [StepBack]: Returns to the previous step
82+
/// - [CloseSurvey]: Closes the survey and reports results
5783
void onEvent(SurveyEvent event) {
5884
if (event is StartSurvey) {
5985
final newState = _handleInitialStep();
@@ -101,7 +127,7 @@ class _SurveyStateProviderWidgetState extends State<SurveyStateProviderWidget> {
101127
);
102128
}
103129

104-
//If not steps are provided we finish the survey
130+
// If no steps are provided we finish the survey
105131
final taskResult = SurveyResult(
106132
id: widget.taskNavigator.task.id,
107133
startTime: _startDate,
@@ -151,8 +177,7 @@ class _SurveyStateProviderWidgetState extends State<SurveyStateProviderWidget> {
151177
final previousStep =
152178
widget.taskNavigator.previousInList(currentState.currentStep);
153179

154-
//If theres no previous step we can't go back further
155-
180+
// If there's no previous step we can't go back further
156181
if (previousStep != null) {
157182
final questionResult = _getResultByStepIdentifier(previousStep.id);
158183

@@ -182,7 +207,7 @@ class _SurveyStateProviderWidgetState extends State<SurveyStateProviderWidget> {
182207
) {
183208
_addResult(event.questionResult);
184209

185-
final stepResults = _results.map((e) => e).toList();
210+
final stepResults = _results.toList();
186211

187212
final taskResult = SurveyResult(
188213
id: widget.taskNavigator.task.id,
@@ -199,9 +224,9 @@ class _SurveyStateProviderWidgetState extends State<SurveyStateProviderWidget> {
199224
);
200225
}
201226

202-
//Currently we are only handling one question per step
227+
// Currently we are only handling one question per step
203228
SurveyState _handleSurveyFinished(PresentingSurveyState currentState) {
204-
final stepResults = _results.map((e) => e).toList();
229+
final stepResults = _results.toList();
205230
final taskResult = SurveyResult(
206231
id: widget.taskNavigator.task.id,
207232
startTime: _startDate,
@@ -235,6 +260,9 @@ class _SurveyStateProviderWidgetState extends State<SurveyStateProviderWidget> {
235260
return widget.taskNavigator.currentStepIndex(step);
236261
}
237262

263+
/// Retrieves a step result by its identifier.
264+
///
265+
/// Returns `null` if no result is found with the given [id].
238266
StepResult? getStepResultById(String id) {
239267
return _results.firstWhereOrNull((element) => element.id == id);
240268
}
@@ -257,8 +285,18 @@ class _SurveyStateProviderWidgetState extends State<SurveyStateProviderWidget> {
257285
}
258286
}
259287

260-
/// InheritedWidget that provides survey state to descendants (immutable)
288+
/// An [InheritedWidget] that provides immutable access to survey state.
289+
///
290+
/// This widget exposes the current survey state, results, and event handling
291+
/// to descendant widgets. It should not be instantiated directly; instead,
292+
/// use [SurveyStateProviderWidget] which manages the mutable state and
293+
/// creates this widget on each rebuild.
294+
///
295+
/// Access this provider using [SurveyStateProvider.of(context)].
261296
class SurveyStateProvider extends InheritedWidget {
297+
/// Creates a [SurveyStateProvider].
298+
///
299+
/// This constructor should only be called by [SurveyStateProviderWidget].
262300
const SurveyStateProvider({
263301
super.key,
264302
required this.state,
@@ -274,17 +312,48 @@ class SurveyStateProvider extends InheritedWidget {
274312
this.stepShell,
275313
});
276314

315+
/// The current state of the survey.
277316
final SurveyState state;
317+
318+
/// The results collected so far in the survey.
319+
///
320+
/// Note: This exposes the internal set. For better encapsulation,
321+
/// consider using this as a read-only reference.
278322
final Set<StepResult> results;
323+
324+
/// The date and time when the survey was started.
279325
final DateTime startDate;
326+
327+
/// Stream controller for broadcasting survey state changes.
328+
///
329+
/// Use `.stream` to listen to state changes:
330+
/// ```dart
331+
/// SurveyStateProvider.of(context).surveyStateStream.stream
332+
/// ```
280333
final StreamController<SurveyState> surveyStateStream;
334+
335+
/// The navigator that manages the task flow.
281336
final TaskNavigator taskNavigator;
282-
final Function(SurveyResult) onResult;
337+
338+
/// Callback invoked when the survey is completed or closed.
339+
final void Function(SurveyResult) onResult;
340+
341+
/// Optional custom shell widget builder for wrapping steps.
283342
final StepShell? stepShell;
343+
344+
/// Global key for managing the Navigator state.
284345
final GlobalKey<NavigatorState> navigatorKey;
346+
347+
/// Callback for handling survey events.
285348
final void Function(SurveyEvent) onEvent;
349+
350+
/// Function to retrieve a step result by its identifier.
286351
final StepResult? Function(String) getStepResultById;
287352

353+
/// Retrieves the [SurveyStateProvider] from the widget tree.
354+
///
355+
/// The [context] must have a [SurveyStateProvider] ancestor.
356+
/// Throws an assertion error if no provider is found.
288357
static SurveyStateProvider of(BuildContext context) {
289358
final result =
290359
context.dependOnInheritedWidgetOfExactType<SurveyStateProvider>();
@@ -294,10 +363,12 @@ class SurveyStateProvider extends InheritedWidget {
294363

295364
@override
296365
bool updateShouldNotify(SurveyStateProvider oldWidget) =>
297-
state != oldWidget.state || results != oldWidget.results;
366+
state != oldWidget.state;
298367

368+
/// Returns the total number of steps in the survey.
299369
int get countSteps => taskNavigator.countSteps;
300370

371+
/// Returns the index of the given [step] in the survey.
301372
int currentStepIndex(Step step) {
302373
return taskNavigator.currentStepIndex(step);
303374
}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: survey_kit
22
description: Create beautiful surveys with Flutter (inspired by iOS ResearchKit Surveys)
3-
version: 1.0.2
3+
version: 1.0.3
44
homepage: https://quickbirdstudios.com
55
repository: https://github.com/quickbirdstudios/survey_kit
66
issue_tracker: https://github.com/quickbirdstudios/survey_kit/issues

0 commit comments

Comments
 (0)