Whenever you're dealing with large forms inside of your Flutter applications, the use of a Stepper Widget may be worth using. To quote the Material Design specification for Steppers:

Steppers convey progress through numbered steps.

How many times have you had to fill out a form and it feels like you've been scrolling forever? I've been in that spot more times than I can count. From the point of view of the developer, this isn't an easy task either. Forms are an eternal struggle, but a necessity for just about every mobile application.

$ flutter create flutter_step && cd flutter_step

$ code .

In order to understand the Stepper, let's take a look at what it's expecting:

Stepper({
  Key key,
  @required this.steps,
  this.physics,
  this.type = StepperType.vertical,
  this.currentStep = 0,
  this.onStepTapped,
  this.onStepContinue,
  this.onStepCancel,
  this.controlsBuilder,
})

The only required thing here is a list of steps. We can create a new Step like this:

Step(
  title: const Text('New Account'),
  isActive: true,
  state: StepState.complete,
  content: Column(
    children: <Widget>[
      TextFormField(
        decoration: InputDecoration(labelText: 'Email Address'),
      ),
      TextFormField(
        decoration: InputDecoration(labelText: 'Password'),
      ),
    ],
  ),
),

Therefore, all we need to do is provide a List<Step> to our Stepper. Without jumping too far forward, we'll house this inside of an AccountPage at lib/pages/account_page.dart:

class AccountPage extends StatefulWidget {
  @override
  _AccountPageState createState() => new _AccountPageState();
}

class _AccountPageState extends State<AccountPage> {
  List<Step> steps = [
    Step(
      title: const Text('New Account'),
      isActive: true,
      state: StepState.complete,
      content: Column(
        children: <Widget>[
          TextFormField(
            decoration: InputDecoration(labelText: 'Email Address'),
          ),
          TextFormField(
            decoration: InputDecoration(labelText: 'Password'),
          ),
        ],
      ),
    ),
    Step(
      isActive: false,
      state: StepState.editing,
      title: const Text('Address'),
      content: Column(
        children: <Widget>[
          TextFormField(
            decoration: InputDecoration(labelText: 'Home Address'),
          ),
          TextFormField(
            decoration: InputDecoration(labelText: 'Postcode'),
          ),
        ],
      ),
    ),
    Step(
      state: StepState.error,
      title: const Text('Avatar'),
      subtitle: const Text("Error!"),
      content: Column(
        children: <Widget>[
          CircleAvatar(
            backgroundColor: Colors.red,
          )
        ],
      ),
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: AppBar(
          title: Text('Create an account'),
        ),
        body: Column(children: <Widget>[
          Expanded(
            child: Stepper(
              steps: steps,
            ),
          ),
        ]));
    }
  }

This gives us a Stepper with three steps, but we currently don't have the ability to go backwards or forwards. We'll need to hook into onStepContinue, onStepCancel and onStepTapped to allow a user to cycle through it.

Let's add a next, cancel and goTo respectively:

// omitted

int currentStep = 0;
bool complete = false;

next() {
  currentStep + 1 != steps.length
      ? goTo(currentStep + 1)
      : setState(() => complete = true);
}

cancel() {
  if (currentStep > 0) {
    goTo(currentStep - 1);
  }
}

goTo(int step) {
  setState(() => currentStep = step);
}

We can add this to our Stepper:

@override
Widget build(BuildContext context) {
  return new Scaffold(
      appBar: AppBar(
        title: Text('Create an account'),
      ),
      body: Column(children: <Widget>[
        Expanded(
          child: Stepper(
          steps: steps,
          currentStep: currentStep,
          onStepContinue: next,
          onStepTapped: (step) => goTo(step),
          onStepCancel: cancel,
          ),
        ),
      ]));
}

Awesome! We've now got the ability to cycle through our Stepper using the CONTINUE and CANCEL buttons.

As there's also the ability to change how the Stepper looks (i.e. vertical or horizontal), let's add a Floating Action Button (FAB) that cycles this too.

We'll need to define a stepperType and then a switchStepType function that goes between two states:

StepperType stepperType = StepperType.horizontal;

switchStepType() {
  setState(() => stepperType == StepperType.horizontal
      ? stepperType = StepperType.vertical
      : stepperType = StepperType.horizontal);
}

// Scaffold
floatingActionButton: FloatingActionButton(
  child: Icon(Icons.list),
  onPressed: switchStepType,
),

Update the Stepper to add the type property equal to our stepperType variable:

Stepper(
  steps: steps,
  type: stepperType,
  currentStep: currentStep,
  onStepContinue: next,
  onStepTapped: (step) => goTo(step),
  onStepCancel: cancel,
),

That gives us the following result(s) when we click our FAB:

Finally, wouldn't it be good if we could show an AlertDialog when we've completed all steps?

There's many ways that we can do this, but for our simple example we'll simply replace the Stepper with an AlertDialog on completion:

@override
Widget build(BuildContext context) {
  return new Scaffold(
    appBar: AppBar(
    title: Text('Create an account'),
    ),
    body: Column(
      children: <Widget>[
        complete ? Expanded(
                child: Center(
                  child: AlertDialog(
                    title: new Text("Profile Created"),
                    content: new Text(
                      "Tada!",
                    ),
                    actions: <Widget>[
                      new FlatButton(
                        child: new Text("Close"),
                        onPressed: () {
                          setState(() => complete = false);
                        },
                      ),
                    ],
                  ),
                ),
              )
            : Expanded(
                child: Stepper(
                  type: stepperType,
                  steps: steps,
                  currentStep: currentStep,
                  onStepContinue: next,
                  onStepTapped: (step) => goTo(step),
                  onStepCancel: cancel,
                ),
              ),
      ],
    ),
    floatingActionButton: FloatingActionButton(
      child: Icon(Icons.list),
      onPressed: switchStepType,
    ),
  );
}

Here's our final result:

Summary

I hope you found this useful! Now go out there and make some awesome steppers.