Search code examples
flutterdartwidgetsetstatedynamicform

StatefulWidget - FLutter


I need to edit this code, in a way to define only one variable widget which can be able to change on every state to a different widget type. I need to be able to make a dynamic form no matter what the question and its type is, the way i handle it is somehow complex and not efficient. so is there any idea on how to change the same variable for different widget on every setState()

    `Column(
                  children: <Widget>[
                    questionText,
                    textCounter > 0 ? textField : SizedBox(),
                    selectCounter > 0 ? selectField : SizedBox()
                  ],
                )),`FutureBuilder(
              future: fetchQuestions(),
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  for (var i = 0; i < snapshot.data.length; i++) {
                    var temp = snapshot.data[i]['question_value'].toString();
                    var type = snapshot.data[i]['question_type'].toString();
                    questionsList.add(temp);
                    typeList.add(type);
                  }

                  return Align(
                    alignment: Alignment.bottomRight,
                    child: RaisedButton(
                      onPressed: () {
                        changeQuest(questionsList, typeList,
                            snapshot.data.length, snapshot.data);
                      },
                      child: Text('next'),
                    ),
                  );
                } else
                  return Center(child: CircularProgressIndicator());
              },
            ),

    changeQuest(List questions, List type, length, data) {
    setState(() {
      textCounter = 0;
      selectCounter = 0;
      integerCounter = 0;
      if (counter < length) {
        questionText = Text(questions[counter]);
        if (type[counter] == 'Integer') {
          textCounter++;
          textField = TextFormField(
            decoration: new InputDecoration(labelText: "Enter your number"),
            keyboardType: TextInputType.number,
            inputFormatters: <TextInputFormatter>[
              WhitelistingTextInputFormatter.digitsOnly
            ], // Only numbers can be entered
          );
        } else if (type[counter] == 'Text') {
          textCounter++;
          textField = TextFormField(
            decoration: new InputDecoration(labelText: "Enter a text"),
            keyboardType: TextInputType.text,
          );
        } else if (type[counter] == 'Select') {
          selectCounter++;
          for (var i = 0; i < data[counter]['answers'].length; i++) {
            answersList
                .add(data[counter]['answers'][i]['answer_value'].toString());
          }
          dropDownValue = answersList[0];
          selectField = DropdownButton<String>(
            value: dropDownValue,
            icon: Icon(Icons.arrow_downward),
            iconSize: 24,
            elevation: 16,
            style: TextStyle(color: Colors.deepPurple),
            underline: Container(
              height: 2,
              color: Colors.deepPurpleAccent,
            ),
            onChanged: (value) {
              setState(() {
               dropDownValue = value;
              });
            },
            items: answersList
                .map<DropdownMenuItem<String>>((String value) {
              return DropdownMenuItem<String>(
                value: value,
                child: Text(value),
              );
            }).toList(),
          );
          print (dropDownValue);
        }
      }

      counter++;
    });
  }

Solution

  • as @proversion said in the comments, you can check in the widget tree, if a condition returns true or false.

    Before you enter the child you could check with an inline if-statement like so: questionType == 'dropdown' ? (Widget for True) : (Widget for False)

    Or if you have to do a complex check, I would do this in the build Method before the return of the widget and set a boolean value there, which represents your check result. Then you can use this value (example: isTrue) in the widget tree like isTure ? (Widget for True) : (Widget for False).

    Here is a sample code, that should work.

    import 'package:flutter/material.dart';
    
    class WidgetWithDifferentChildren extends StatefulWidget {
      @override
      _WidgetWithDifferentChildrenState createState() =>
          _WidgetWithDifferentChildrenState();
    }
    
    class _WidgetWithDifferentChildrenState
        extends State<WidgetWithDifferentChildren> {
      String questionType = '';
      String dropdownValue = 'SelectItem';
      String textValue = '';
      TextEditingController txtCtrl = TextEditingController();
    
      @override
      void dispose() {
        // TODO: implement dispose when using TextEditingController
        txtCtrl.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Container(
          child: questionType == ''
              ? Text('no Question Type')
              : questionType == 'dropdown'
                  ? DropdownButton<String>(
                      value: dropdownValue,
                      onChanged: (String newValue) {
                        // Do something with the new Value
                        print('New DropDown value = $newValue');
                        setState(() {
                          dropdownValue = newValue;
                        });
                      },
                      items: <String>[
                        'SelectItem',
                        'Item 1',
                        'Item 2',
                        'Item 3',
                      ].map<DropdownMenuItem<String>>((String value) {
                        return DropdownMenuItem<String>(
                          value: value,
                          child: new Text(value),
                        );
                      }).toList(),
                    )
                  : questionType == 'textfield'
                      ? TextFormField(
                          controller: txtCtrl,
                          onChanged: (value) {
                            // Do something with the new Value
                            print('New TextField value = $value');
                            setState(() {
                              textValue = value;
                            });
                          },
                        )
                      : Text('Question Type does not match'),
        );
      }
    }
    
    

    UPDATE

    acc. to your provided code, you may want to check the following. I created a separate class which will return the right widget for the question. Just pass the type and additional the dropDownList to the function.

    General I would suggest to store the questions and the corresponding answers in the same array, this would be a easy call of the function like getInputWidget(type:question[i].type, dropDownList:question[i].dropDownList).

    enter image description here

    Source Code for above example

    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    
    class WidgetWithDifferentChildren extends StatefulWidget {
      @override
      _WidgetWithDifferentChildrenState createState() =>
          _WidgetWithDifferentChildrenState();
    }
    
    class _WidgetWithDifferentChildrenState
        extends State<WidgetWithDifferentChildren> {
      String questionType = '';
      String inputValue = '';
      List<String> answers = [];
      int questionID = 0;
      TextEditingController txtCtrl = TextEditingController();
    
      List<Map<String, String>> questionList = [
        {'question_value': 'text question ', 'question_type': 'text'},
        {'question_value': 'number question ', 'question_type': 'number'},
        {'question_value': 'select question ', 'question_type': 'select'},
        {'question_value': 'last question ', 'question_type': 'text'},
      ];
      List<String> dropDownList = [
        'Select an Item',
        'Answer A',
        'Answer B',
        'Answer C',
      ];
    
      @override
      void dispose() {
        // TODO: implement dispose when using TextEditingController
        txtCtrl.dispose();
        super.dispose();
      }
    
      Widget getInputWidget({@required String type, List<String> dropDownList}) {
        Widget inputW;
        if (type == 'number' || type == 'text') {
          inputW = TextFormField(
            controller: txtCtrl,
            decoration: new InputDecoration(labelText: "Enter a $type"),
            keyboardType:
                type == 'text' ? TextInputType.text : TextInputType.number,
            inputFormatters: <TextInputFormatter>[
              type == 'text'
                  ? LengthLimitingTextInputFormatter(50)
                  : WhitelistingTextInputFormatter.digitsOnly
            ], // Only numbers can be entered
            onChanged: (value) {
              setState(() {
                inputValue = value;
              });
            },
          );
        } else if (type == 'select') {
          if (inputValue.length == 0) {
            // set the input Value for the first time
            inputValue = dropDownList[0];
          }
          inputW = DropdownButton<String>(
            value: inputValue,
            icon: Icon(Icons.arrow_downward),
            iconSize: 24,
            elevation: 16,
            style: TextStyle(color: Colors.deepPurple),
            underline: Container(
              height: 2,
              color: Colors.deepPurpleAccent,
            ),
            onChanged: (value) {
              setState(() {
                inputValue = value;
              });
            },
            items: dropDownList.map<DropdownMenuItem<String>>((String value) {
              return DropdownMenuItem<String>(
                value: value,
                child: Text(value),
              );
            }).toList(),
          );
        }
    
        return inputW;
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Padding(
            padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 30),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                RaisedButton(
                  onPressed: () {
                    setState(() {
                      answers.add(inputValue);
                      inputValue = '';
                      txtCtrl.clear();
                      questionID = questionID + 1;
                    });
    
                    // unfocus to close the Keyboard
                    // conrtibution to: https://flutterigniter.com/dismiss-keyboard-form-lose-focus/
                    FocusScopeNode currentFocus = FocusScope.of(context);
                    if (!currentFocus.hasPrimaryFocus) {
                      currentFocus.unfocus();
                    }
                  },
                  child: Text('next'),
                ),
                getInputWidget(
                    type: questionList[questionID]['question_type'],
                    dropDownList: dropDownList),
                Divider(thickness: 2),
                Text('You enter: $inputValue'),
                Divider(thickness: 2),
                Text('Your answers are:'),
                Flexible(
                  child: ListView.builder(
                      itemCount: answers.length,
                      itemBuilder: (context, index) {
                        return ListTile(
                          title: Text('$index. ${answers[index]}'),
                        );
                      }),
                ),
              ],
            ),
          ),
        );
      }
    }