Search code examples
flutterflutter-layoutstepper

Flutter - How to fill remaining space in Stepper controls builder?


In the screenshot, I want to get the next and back buttons to the bottom of the screen.

enter image description here

The stepper has a parameter, controlsBuilder that allows you to build out the layout for the controls. If it's just a simple row, It's placed right underneath the content.

Apparently, the Stepper is a flexible wrapper. I'm not sure what that means. I think it means that the Stepper is considered a flex object because it contains a scrollable area (for the content). Having read the docs, if I'm understanding correctly, it says that I cannot use an Expanded or a Column with a max size in the mainAxis because the stepper is essentially a scrollable area, meaning any RenderBox inside it has unbounded constraints.

So, what are some ways the controls builder can be pushed down to the bottom?

Widget _createEventControlBuilder(BuildContext context, {VoidCallback onStepContinue, VoidCallback onStepCancel}) {
return Row(
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
    children: <Widget>[
      FlatButton(
        onPressed: onStepCancel,
        child: const Text('BACK'),
      ),
      FlatButton(
        onPressed: onStepContinue,
        child: const Text('NEXT'),
      ),
    ]
);
  }

I did try wrapping the above row in a LayoutBuilder as well as another attempt using a SizedBox, setting the height to MediaQuery.of(context).size.height;. It does push it near to the bottom (not quite as much as I like), but the problem is that now there is space beneath controls, causing the screen to scroll down into empty space.

Full code:

    @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
    title: Text("Create an Event"),
  ),
  body: Form(
    key: _eventFormKey,
    child: Stepper(
        type: StepperType.horizontal,
        currentStep: _currentStep,
        controlsBuilder: _createEventControlBuilder,
        onStepContinue: () {
          if (_currentStep + 1 >= MAX_STEPS)
            return;
          setState(() {
            _currentStep += 1;
          });
          },
        onStepCancel: () {
          if (_currentStep + 1 >= MAX_STEPS)
            return;
          setState(() {
            _currentStep -= 1;
          });
        },
        steps: <Step>[
          Step(
            title: Text("Name"),
            isActive: 0 == _currentStep,
            state: _getStepState(0),
            content: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Container(
                  margin: EdgeInsets.only(bottom: 10.0),
                  child: Text(
                    "Give your event a cool name",
                    style: Theme.of(context).textTheme.title,
                  ),
                ),
                TextFormField(
                  maxLines: 1,
                  maxLength: 50,
                  maxLengthEnforced: true,
                  decoration: InputDecoration(
                    hintText: "e.g. Let's eat cheeseburgers!",
                  ),
                  validator: (value) {
                    if (value.trim().isEmpty)
                      return "Event name required.";
                  },
                )
              ],
            )
          ),

          Step(
            title: Text("Type"),
            isActive: 1 == _currentStep,
            state: _getStepState(1),
            content: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Container(
                  margin: EdgeInsets.only(bottom: 10.0),
                  child: Text(
                    "Select an event type",
                    style: Theme.of(context).textTheme.title,
                  ),
                ),
                Container(
                  margin: EdgeInsets.only(bottom: 10.0),
                  child: Row(
                    children: <Widget>[
                      Expanded(
                        child: DropdownButton<int>(
                            items: _stepTwoDropdownItems,
                            hint: Text("Select event type"),
                            isExpanded: true,
                            value: _eventTypeSelectedIndex,
                            onChanged: (selection) {
                              setState(() {
                                _eventTypeSelectedIndex = selection;
                              });
                            }),
                      )
                    ],
                  )
                )
              ],
            )
          ),
      ]
    ),
  ),
);
}

Solution

  • I think you can create your own Stepper , or you can try this 'hack' :

    Create two variables to store the callbacks:

          VoidCallback _onStepContinue;
          VoidCallback _onStepCancel;
    

    Put your Form inside a Stack :

            Stack(
                    children: <Widget>[
                      Form(
                        child: Stepper(
    

    Change your createEventControlBuilder method:

              Widget _createEventControlBuilder(BuildContext context,
                  {VoidCallback onStepContinue, VoidCallback onStepCancel}) {
                _onStepContinue = onStepContinue;
                _onStepCancel = onStepCancel;
                return SizedBox.shrink();
              }
    

    Add your custom buttoms :

          Widget _bottomBar() {
            return Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  FlatButton(
                    onPressed: () => _onStepCancel(),
                    child: const Text('BACK'),
                  ),
                  FlatButton(
                    onPressed: () => _onStepContinue(),
                    child: const Text('NEXT'),
                  ),
                ]);
          } 
    

    This is how your Stack will looks like :

        Stack(
                    children: <Widget>[
                      Form(
                        child: Stepper(
                        ....
                       ), //Form
                        Align(
                        alignment: Alignment.bottomCenter,
                        child: _bottomBar(),
                        )
    

    I know this is a little dirty, but you could try , otherwise I recommend you to create your own Widget.