How to make a flexible list layout with an anchored button in Flutter?

Viewed 1779

enter image description here

I'm building a polling form, in which the user specifies a question and a flexible number of choices. There is a "Send" button at the bottom.

However, I run into issues when interacting with the form. Adding too many choices causes the ListView to draw underneath the Send button. Tapping on a textfield in the lower half of the screen results in the selected field being obscured by the keyboard and Send button. Here is the code:

@override
  Widget build(BuildContext context) {
     return Scaffold(
        //resizeToAvoidBottomPadding: false, // keyboard will cover floating elements
        appBar: AppBar(title: Text('Add Question')),
        body: Container(
           padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0),
           child: Column(
              mainAxisAlignment: MainAxisAlignment.start,
              children: <Widget>[
                 Expanded(
                    child: ListView(
                       //crossAxisAlignment: CrossAxisAlignment.stretch,
                       children: <Widget>[
                          TextFormField(
                             decoration: InputDecoration(labelText: 'Question'),
                             initialValue: _questionText,
                             validator: (value) {
                                if (value.isEmpty) {
                                   return 'Please enter some text';
                                }
                             },
                          ),
                          SizedBox(height: 16.0),
                          Center(
                             child: Text('Choices'),
                          ),
                          SizedBox(height: 16.0),

                          buildForm(),

                          SizedBox(height: 16.0),
                          RaisedButton.icon(
                             icon: Icon(Icons.add),
                             label: Text('Add Choice'),
                             onPressed: () {
                                final x = _questionChoices.length;
                                _addChoice("Test $x");
                             },
                          ),
                          SizedBox(height: 16.0),
                       ],
                    ),
                 ),
                 RaisedButton(
                    child: Text('SEND QUESTION'),
                    onPressed: () {
                       _sendQuestion();
                    },
                 )
              ],
           ),
        ),
     );
  }

  Widget buildForm() {
     List<Widget> list = new List();
     for (var index = 0; index < _questionChoices.length; index++) {
        list.add(Row(
           crossAxisAlignment: CrossAxisAlignment.center,
           children: <Widget>[
              IconButton(
                 icon: Icon(Icons.remove_circle),
                 color: Colors.deepOrange,
                 tooltip: 'Remove Choice',
                 onPressed: () {
                    _removeChoice(index);
                 },
              ),
              Flexible(
                 child: TextFormField(
                    decoration: InputDecoration(hintText: 'Enter choice text.'),
                    initialValue: _questionChoices[index],
                    validator: (val) {
                       val.length == 0 ? 'Please enter a value.' : null;
                    },
                    onSaved: (val) => _questionChoices[index] = val,
                 ))
           ],
        ));
        list.add(SizedBox(
           height: 12.0,
        ));
     }
     return Column(children: list);
  }
2 Answers

There is a property in your Scaffold named bottomNavigationBar where you can use to put your button.

   return Scaffold(
           bottomNavigationBar: BottomAppBar(
                      child: RaisedButton(
                         child: Text('SEND QUESTION'),
                         onPressed: () {
                         _sendQuestion();
                      },
                   ),
              ),
               ...

don't use Expanded and/or don't put the button outside of the ListView. What you're doing in the above example is effectively defining a view that won't expand beyond the height of the screen, making the Expanded section the widget that shrinks its height to conform.

As an aside, I've found myself putting submit buttons at the top of the screen in a lot of instances. I think it makes UI/UX sense: a user inherently understands the variable height stuff expands below, the call to action stuff presents itself in the first screen view so they don't assume a save upon a change, and it mitigates a lot of obscuring keyboard problems.

Related