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++;
});
}
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'),
);
}
}
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)
.
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]}'),
);
}),
),
],
),
),
);
}
}