Repository: flutter
Issue: Stepper lazy loading form fields and ignoring form field methods
Labels: framework,f: material design,has reproducible steps,found in release: 3.3,found in release: 3.6,team-design,triaged-design

## Steps to Reproduce

<!-- Please include full steps to reproduce so that we can reproduce the problem. -->

I created a minimal reporducible example as a repository. ([see here](https://github.com/S-ecki/flutter_form_stepper_bug)). Please clone the code and execute `flutter run`, preferrably on a mobile device to easier recognize the problem.

1. Having step 0 open, click save
2. Having step 10 (or the last step) open, click save

**Expected results:** <!-- what did you want to see? -->
The map printed to the console should have `fieldsPerStep` (10 per default) as value for each key

**Actual results:** <!-- what did you see? -->
First print: The map has 10 as value for the top x steps, and 0 for the last x (depending on screen height)
Second print: Vice verca, bottom fields are 10 while top are 0

<details>
<summary>Code sample</summary>

<!--
      Please create a minimal reproducible sample that shows the problem
      and attach it below between the lines with the backticks.

      To create it you can use `flutter create bug` command and update the `main.dart` file.

      Alternatively, you can use https://dartpad.dev/
      which is capable of creating and running small Flutter apps.

      Without this we will unlikely be able to progress on the issue, and because of that
      we regretfully will have to close it.
-->
Using my repository will be a better experience, the readme also explains the problem in more details. [See here](https://github.com/S-ecki/flutter_form_stepper_bug)

```dart
import 'package:flutter/material.dart';

const numSteps = 10;
const fieldsPerStep = 10;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
[](url)
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(child: FormStepper()),
      ),
    );
  }
}

class FormStepper extends StatefulWidget {
  const FormStepper({super.key});

  @override
  State<FormStepper> createState() => _FormStepperState();
}

class _FormStepperState extends State<FormStepper> {
  late final GlobalKey<FormState> _formKey;
  int _currentStep = 0;
  // Maps step number to the number of fields in that step for which .save()
  // has been called.
  late Map<int, int> _savedFieldsPerStep;

  @override
  void initState() {
    super.initState();
    _formKey = GlobalKey<FormState>();
    _resetMap();
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Stepper(
        steps: _generateSteps(),
        currentStep: _currentStep,
        onStepTapped: (i) => setState(() => _currentStep = i),
        controlsBuilder: (context, details) => ElevatedButton(
          onPressed: () {
            _formKey.currentState?.save();
            print(_savedFieldsPerStep);
            _resetMap();
          },
          child: const Text('Save'),
        ),
      ),
    );
  }

  List<Step> _generateSteps() {
    return List.generate(
      numSteps,
      (stepIndex) => Step(
        title: Text('Step $stepIndex'),
        content: Column(
          children: List.generate(
            fieldsPerStep,
            (index) => TextFormField(
              onSaved: (newValue) => _incrementMapValue(stepIndex),
            ),
          ),
        ),
      ),
    );
  }

  void _incrementMapValue(int stepIndex) {
    _savedFieldsPerStep[stepIndex] = _savedFieldsPerStep[stepIndex]! + 1;
  }

  void _resetMap() {
    _savedFieldsPerStep = {
      for (var i in List.generate(numSteps, (i) => i)) i: 0
    };
  }
}
```

</details>

Copied from ReadMe to better explain the problem:

### Problem
When working with a lengthy `Form` widget inside a `Stepper` widget with multiple `Step`s, only form fields visible are recognized. That leads to wrongfully ignoring methods called on those form fields.<br>
This is especially problematic if, for example, you fill out a field in step 1, go to the last step, fill something out there and call `.save()`. If many steps are present, the value filled out in the top field will not be saved.

### The Reason
The Material `Stepper` widget internally uses a `ListView` to show its `Step`s. `ListView` (in contrast to `SingleChildScrollView`) uses lazy loading for its items, which means that `Step`s currently not visible in the `Stepper` are ignored. In case of having `FormField`s inside the `Step`s, their methods (like `.save()`) are not called. 

### Minimal Reproducible Example
I created this repository to show the bug in a minimal reproducible way. <br>
In _form_stepper.dart_, a `Stepper` with 10 `Step`s and 10 `TextFormField`s per Step is created. <br>
The `_savedFieldsPerStep` map is used to showcase the problem: The `.save()` method of each `TextFormField` should increment the value for its step number as key. This means, when saving the form, each key of the map should have 10 as value. <br>
However, what you will witness is that saving at the top will leave the bottom steps at 0 saved fields and vice versa.

### Proposed Solution
A way to prevent the faulty lazy-loading mechanism of the `Stepper` would be to use `SingleChildScrollView` instead of `ListView` internally. <br>
However, this would lead to problems with performance, especially for usages of the `Stepper` where lazy loading is actually desired. <br>
Therefore, I propose to add a `bool` parameter to the `Stepper` widget, which allows to disable lazy loading and switch the internal `ListView` to a `SingleChildScrollView` with a `Column` as its child.