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'),
);
}
}
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).
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]}'),
);
}),
),
],
),
),
);
}
}
Related
I ran this code and it returns an error : RenderCustomMultiChildLayoutBox object was given an infinite size during layout. I couldn't figure out which widget has the error. This is my code.
import 'package:flutter/material.dart';
import '../mediaquery.dart';
class DropdownTextfield extends StatefulWidget {
#override
_DropdownTextfieldState createState() => _DropdownTextfieldState();
}
class _DropdownTextfieldState extends State<DropdownTextfield> {
var properties = [
'kg',
'yd',
'm',
];
String dropdownvalue = 'kg';
String _property1 = '';
String _property2 = '';
String _property3 = '';
bool _isOptionSelected = false;
TextEditingController quantitycontroller = TextEditingController();
var selectedOption;
TextEditingController textfieldValue = TextEditingController();
final List<String> options = [];
#override
void initState() {
super.initState();
selectedOption = options.isNotEmpty ? options[0] : null;
}
#override
Widget build(BuildContext context) {
final screenHeight = ScreenInfo.screenHeight(context);
final screenWidth = ScreenInfo.screenWidth(context);
return Scaffold(
body: SingleChildScrollView(
child: SizedBox(
height: screenHeight * 0.8,
child: Column(
children: [
TextField(
onChanged: (value) {
setState(() {
textfieldValue.text = value;
});
},
),
DropdownButton<String>(
value: selectedOption,
onChanged: (value) {
setState(() {
selectedOption = value!;
_isOptionSelected = true;
});
},
hint: const Text('Input from Text Field Above'),
items: options.map((option) {
return DropdownMenuItem<String>(
value: option,
child: Text(option),
);
}).toList(),
),
TextButton(
onPressed: () {
setState(() {
options.add(textfieldValue.text);
});
},
child: Text("Add Option"),
),
Visibility(
visible: _isOptionSelected,
child: Column(
children: <Widget>[
Row(
children: [
TextField(
controller: quantitycontroller,
decoration: InputDecoration(labelText: "Quantity"),
onChanged: (value) {
setState(() {
_property1 = value;
});
},
),
const SizedBox(width: 10,),
DropdownButton(
value: dropdownvalue,
items: properties.map((properties) {
return DropdownMenuItem(
value: properties,
child: Text(properties),
);
}).toList(),
onChanged: (String? newValue){
setState(() {
dropdownvalue = newValue!;
});
},
)
],
),
],
),
),
],
),
),
),
);
}
}
I tried to use a Container and a SizedBox with fixed height as shown above but it doesn't seem to do anything. I also tried wrapping the children of the Row widget with Expanded and Flexible but those too doesn't seem to fix it. How can I fix this?
TextField is trying to get infinite width and Row widget providing it. Wrap your TextFieldwith Expanded widget to get available space.
Expanded(
child: TextField(
controller: quantitycontroller,
I'm using a form to create multiples vehicle entries.
every time I click on the floating button it adds a vehicleform to the page one on top of the other. "A List type that receives widgets"
If I try to select the "leased radio option", the form doesn't update, and if hit the switch button right below, nothing happens again. BUT! Curiously if I hit any of the Dropdown State....it works for them (only the dropdown gets updated). and BUT! number 2: If I hit the floating button to add a new vehicle form, the changes made on the previous form gets carried to the new form. My theory is that the buttons are working under the hood, but the setStates are no running correctly
On the main_page.dart there is a stateful widget that calls vehicles_page() which holds all the scaffold and widgets for that form including a dropdown list which is called from a 3rd file(dropdown_forms.dart).
To guide towards the right direction, just lay your eyes at the end of the code on the build() function.
FYI - Flutter Doctor -v returned no errors
Yes -I'm using stateful widgets
Yes - I'm using setstates to update
No - I'm not a professional programmer, I'm a super beginner on flutter and this is my 3rd week playing with flutter
After running some tests.... if I remove them from the listview widget, it works fine.
main.dart
This is the main file
import 'package:flutter/material.dart';
import 'package:learningflutter/screens/parties_page.dart';
import 'package:learningflutter/screens/vehicles_page.dart';
import 'package:learningflutter/screens/incident_page.dart';
// import 'package:learningflutter/parties.dart';
enum VehicleType { pov, leased, pgti }
void main(List<String> args) {
runApp(const VortexEHS());
}
class VortexEHS extends StatelessWidget {
const VortexEHS({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'VortexEHS',
theme: ThemeData(
primarySwatch: Colors.teal,
),
home: _MainPage(),
);
}
}
class _MainPage extends StatefulWidget {
_MainPage({Key? key}) : super(key: key);
#override
State<_MainPage> createState() => __MainPageState();
}
class __MainPageState extends State<_MainPage> {
// int currentPageIndex = 1;
final screens = [
PartiesPage(),
VehiclesPage(),
FormIncident(),
FormIncident()
];
#override
Widget build(BuildContext context) {
return Container(child: VehiclesPage());
}
}
vehicles_page.dart (Code Below)
// ignore_for_file: unused_element, unused_field
import 'package:flutter/material.dart';
import '../dropdown_forms.dart';
enum VehicleType { pov, leased, pgti }
class VehiclesPage extends StatefulWidget {
const VehiclesPage({Key? key}) : super(key: key);
#override
State<VehiclesPage> createState() => _VehiclesPageState();
}
class _VehiclesPageState extends State<VehiclesPage> {
//Variables
VehicleType vehicleType = VehicleType.pov;
bool isCommercial = false;
String? _make;
String? _model;
String? _year;
String? _color;
String? _vimNumber;
String? _plate;
String? _ownerName;
String? _ownerAddress;
String? _ownerCity;
String? _ownerState;
String? _ownerZip;
String? _ownerPhone;
String? _insuranceCoName;
String? _insuranceCoPhone;
String? _policyHolderName;
String? _policyNumber;
List<Widget> vehicles = [];
Widget buildTypeVeihicle() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: ListTile(
horizontalTitleGap: 0,
dense: true,
title: const Text('POV'),
leading: Radio(
value: VehicleType.pov,
groupValue: vehicleType,
onChanged: (VehicleType? value) {
setState(() {
vehicleType = value!;
});
}),
),
),
Expanded(
child: ListTile(
horizontalTitleGap: 0,
dense: true,
title: const Text('Leased'),
leading: Radio(
value: VehicleType.leased,
groupValue: vehicleType,
onChanged: (VehicleType? value) {
setState(() {
vehicleType = value!;
});
}),
),
),
Expanded(
child: ListTile(
horizontalTitleGap: 0,
dense: true,
title: const Text(
'PGTI',
softWrap: false,
),
leading: Radio(
value: VehicleType.pgti,
groupValue: vehicleType,
onChanged: (VehicleType? value) {
setState(() {
vehicleType = value!;
});
}),
),
),
],
);
}
Widget _buildIsCommercial() {
return SwitchListTile(
title: const Text('This is commercial vehicle?'),
value: isCommercial,
onChanged: (bool value) {
setState(() {
isCommercial = value;
});
},
// secondary: const Icon(Icons.car_repair),
);
}
Widget _buildVehicleMake() {
return TextFormField(
decoration: const InputDecoration(labelText: 'Make'),
validator: (String? value) {
if (value == null) {
return 'Make is required';
}
return null;
},
onSaved: (String? value) {
_make = value;
},
);
}
Widget _buildVehicleModel() {
return TextFormField(
decoration: const InputDecoration(labelText: 'Model'),
validator: (String? value) {
if (value == null) {
return 'Model is required';
}
return null;
},
onSaved: (String? value) {
_model = value;
},
);
}
Widget _buildVehicleYear() {
return TextFormField(
decoration: const InputDecoration(labelText: 'Year'),
validator: (String? value) {
if (value == null) {
return 'Year is required';
}
return null;
},
onSaved: (String? value) {
_year = value;
},
);
}
Widget _buildVehicleColor() {
return TextFormField(
decoration: const InputDecoration(labelText: 'Color'),
validator: (String? value) {
if (value == null) {
return 'Color is required';
}
return null;
},
onSaved: (String? value) {
_color = value;
},
);
}
Widget _buildVehicleVinNumber() {
return TextFormField(
decoration: const InputDecoration(labelText: 'Vin Number'),
validator: (String? value) {
if (value == null) {
return 'Vin Number is required';
}
return null;
},
onSaved: (String? value) {
_vimNumber = value;
},
);
}
Widget _buildVehiclePlate() {
return TextFormField(
decoration: const InputDecoration(labelText: 'Plate Number'),
validator: (String? value) {
if (value == null) {
return 'Plate Number is required';
}
return null;
},
onSaved: (String? value) {
_vimNumber = value;
},
);
}
Widget _buildOwnerName() {
return TextFormField(
decoration: const InputDecoration(labelText: 'Owner Name'),
validator: (String? value) {
if (value == null) {
return 'Owner Name is required';
}
return null;
},
onSaved: (String? value) {
_vimNumber = value;
},
);
}
Widget _buildOwnerAddress() {
return TextFormField(
decoration: const InputDecoration(labelText: 'Owner Address'),
validator: (String? value) {
if (value == null) {
return 'Owner Address is required';
}
return null;
},
onSaved: (String? value) {
_vimNumber = value;
},
);
}
Widget _buildOwnerCity() {
return TextFormField(
decoration: const InputDecoration(labelText: 'Owner City'),
validator: (String? value) {
if (value == null) {
return 'Owner City is required';
}
return null;
},
onSaved: (String? value) {
_vimNumber = value;
},
);
}
Widget _buildOwnerZip() {
return TextFormField(
decoration: const InputDecoration(labelText: 'Owner Zip'),
validator: (String? value) {
if (value == null) {
return 'Owner Zip is required';
}
return null;
},
onSaved: (String? value) {
_vimNumber = value;
},
);
}
Widget _buildOwnerPhone() {
return TextFormField(
decoration: const InputDecoration(labelText: 'Owner Phone'),
validator: (String? value) {
if (value == null) {
return 'Owner Phone is required';
}
return null;
},
onSaved: (String? value) {
_vimNumber = value;
},
);
}
Widget _buildInsuranceCo() {
return TextFormField(
decoration: const InputDecoration(labelText: 'Owner Insurance Company'),
validator: (String? value) {
if (value == null) {
return 'Owner Insurance Company is required';
}
return null;
},
onSaved: (String? value) {
_vimNumber = value;
},
);
}
// ignore: unused_element
Widget _buildInsurancePhone() {
return TextFormField(
decoration: const InputDecoration(labelText: 'Insurance Phone'),
validator: (String? value) {
if (value == null) {
return 'Insurance Phone is required';
}
return null;
},
onSaved: (String? value) {
_vimNumber = value;
},
);
}
Widget _buildPolicyHolderName() {
return TextFormField(
decoration: const InputDecoration(labelText: 'Policy Holder'),
validator: (String? value) {
if (value == null) {
return 'Policy Holder is required';
}
return null;
},
onSaved: (String? value) {
_vimNumber = value;
},
);
}
Widget _buildPolicyNumber() {
return TextFormField(
decoration: const InputDecoration(labelText: 'Policy Number'),
validator: (String? value) {
if (value == null) {
return 'Policy Number is required';
}
return null;
},
onSaved: (String? value) {
_vimNumber = value;
},
);
}
Widget _buildVehiclesCard() {
return Container(
margin: const EdgeInsets.all(8.0),
decoration: const BoxDecoration(
boxShadow: [
BoxShadow(color: Colors.blueGrey, blurRadius: 10, spreadRadius: -10)
],
),
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
child: Container(
margin: const EdgeInsets.fromLTRB(0, 0, 0, 10),
padding: const EdgeInsets.all(10),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Icon(Icons.directions_car, color: Colors.teal),
IconButton(
onPressed: () {
setState(() {
print('Trash presses');
});
},
icon: const Icon(Icons.delete),
color: Colors.red,
),
],
),
buildTypeVeihicle(),
_buildIsCommercial(),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Expanded(child: _buildVehicleMake()),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 0, 0),
child: _buildVehicleModel()))
]),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Expanded(child: _buildVehicleYear()),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 0, 0),
child: _buildVehicleColor()))
]),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Expanded(child: _buildVehicleVinNumber()),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(8.0, 0, 0, 0),
child: _buildVehiclePlate(),
)),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 20, 0, 0),
child: DropdownStatesUs())),
]),
const SizedBox(
height: 8,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: const [
Icon(
Icons.person,
color: Colors.teal,
),
],
),
Row(
children: [
Expanded(child: _buildOwnerName()),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 0, 0),
child: _buildOwnerPhone(),
)),
],
),
_buildOwnerAddress(),
Row(
children: [
Expanded(child: _buildOwnerCity()),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 20, 0, 0),
child: DropdownStatesUs(),
)),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 0, 0),
child: _buildOwnerZip(),
)),
],
),
Row(
children: [
Expanded(child: _buildPolicyHolderName()),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 0, 0),
child: _buildPolicyNumber(),
)),
],
)
],
),
),
),
);
}
//
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.teal,
child: const Icon(Icons.add),
onPressed: () {
vehicles.add(_buildVehiclesCard());
// print(vehicles.length);
setState(() {});
},
),
body: Form(
child: ListView.builder(
primary: false,
itemCount: vehicles.length,
itemBuilder: (BuildContext context, int i) {
return vehicles[i];
},
),
),
);
}
}
Thanks for any help beforehand, any other advices or tips to make the code better, let me know
The current state changes behavior reflected on newly item because last item created before the radio-buttom or switch tile was changed. Once you switch the button and change the radioGroup value it will only get to the new created widget that will be trigger by floating action button.
Notice that List<Widget> vehicles = []; holds the created widgets. And while creating new widget you are using state variables like vehicleType and isCommercial. Once you click on 1st generated widget, these variables get new data based on your tap event. while the state is holding these variables, then again you click on fab to add item on vehicles to generate item with current state of vehicleType and isCommercial.
While every list-item will have its own state, it is better to create a new StatefulWidget for each item. Also, you can go for state-management like riverpod, bloc for future purpose.
A simplify version but not complete, you need to handle callback to get changes data or better start using riverpod or bloc for state management
enum VehicleType { pov, leased, pgti }
class VehiclesPage extends StatefulWidget {
const VehiclesPage({Key? key}) : super(key: key);
#override
State<VehiclesPage> createState() => _VehiclesPageState();
}
class _VehiclesPageState extends State<VehiclesPage> {
List<MyDataModel> vehicles = [];
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.teal,
child: const Icon(Icons.add),
onPressed: () {
vehicles.add(MyDataModel());
setState(() {});
},
),
body: Form(
child: ListView.builder(
primary: false,
itemCount: vehicles.length,
itemBuilder: (BuildContext context, int i) {
return ListItem(model: vehicles[i]);
},
),
),
);
}
}
class MyDataModel {
final VehicleType vehicleType;
final bool isCommercial;
MyDataModel({
this.vehicleType = VehicleType.pov,
this.isCommercial = false,
});
MyDataModel copyWith({
VehicleType? vehicleType,
bool? isCommercial,
}) {
return MyDataModel(
vehicleType: vehicleType ?? this.vehicleType,
isCommercial: isCommercial ?? this.isCommercial,
);
}
}
class ListItem extends StatefulWidget {
final MyDataModel model;
const ListItem({
Key? key,
required this.model,
}) : super(key: key);
#override
State<ListItem> createState() => _ListItemState();
}
class _ListItemState extends State<ListItem> {
late MyDataModel model = widget.model;
Widget buildTypeVeihicle() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: ListTile(
horizontalTitleGap: 0,
dense: true,
title: const Text('POV'),
leading: Radio(
value: VehicleType.pov,
groupValue: model.vehicleType,
onChanged: (VehicleType? value) {
setState(() {
model = model.copyWith(vehicleType: value);
});
}),
),
),
Expanded(
child: ListTile(
horizontalTitleGap: 0,
dense: true,
title: const Text('Leased'),
leading: Radio(
value: VehicleType.leased,
groupValue: model.vehicleType,
onChanged: (VehicleType? value) {
setState(() {
model = model.copyWith(vehicleType: value);
});
}),
),
),
Expanded(
child: ListTile(
horizontalTitleGap: 0,
dense: true,
title: const Text(
'PGTI',
softWrap: false,
),
leading: Radio(
value: VehicleType.pgti,
groupValue: model.vehicleType,
onChanged: (VehicleType? value) {
setState(() {
model = model.copyWith(vehicleType: value);
});
}),
),
),
],
);
}
Widget _buildIsCommercial() {
return SwitchListTile(
title: const Text('This is commercial vehicle?'),
value: model.isCommercial,
onChanged: (bool value) {
setState(() {
model = model.copyWith(isCommercial: value);
});
},
// secondary: const Icon(Icons.car_repair),
);
}
Widget _buildVehiclesCard() {
return Container(
margin: const EdgeInsets.all(8.0),
decoration: const BoxDecoration(
boxShadow: [
BoxShadow(color: Colors.blueGrey, blurRadius: 10, spreadRadius: -10)
],
),
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
child: Container(
margin: const EdgeInsets.fromLTRB(0, 0, 0, 10),
padding: const EdgeInsets.all(10),
child: Column(
children: [
buildTypeVeihicle(),
_buildIsCommercial(),
],
),
),
),
);
}
#override
Widget build(BuildContext context) {
return _buildVehiclesCard();
}
}
I created a textfield named chargesMax and add a ListTile widget which contains checkbox and two textfield named charges and time. I want when user enter some value on chargesMax textfield and then check any checkbox then charges textfield should contain the value of chargesMax textfield. and then if user modify then charges textfield then only this field will modify not all the textfield of listTile.
here is my code
charges.text = chargesMax.text;
In this above line i'm trying to do what i want but when i check the checkbox of any listTile, value of chargesMax display to charges textfield but all textfield of checkbox modify on check on any checkbox. wherease, i can check the checkbox indiviually.
Full code:
TextEditingController time = TextEditingController();
TextEditingController charges = TextEditingController();
TextEditingController chargesMax = TextEditingController();
List _selectedCities = [];
void _onCitySelected(bool selected, cityId) {
if (selected == true) {
setState(() {
charges.text = chargesMax.text;
_selectedCities.add(cityId);
print(_selectedCities);
});
} else {
setState(() {
_selectedCities.remove(cityId);
});
}
}
Widget build(BuildContext context) {
return SafeArea(
child: Container(
child: Column(
children: [
//chargesMax textfield
textformfieldCustomwithouticon(
context,
TextInputType.number,
MediaQuery.of(context).size.width * 0.9,
chargesMax, (String value) {
setState(() {
chargesMax.text = value;
chargesMax.selection = TextSelection.fromPosition(
TextPosition(offset: chargesMax.text.length));
});
}, (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
}, 'Enter delivery charges for maximumn destination',
'Delivery charges', 55.0),
SizedBox(
height:
MediaQuery.of(context).size.height * 0.6,
child: ListView.builder(
itemCount: books.length,
itemBuilder: (context, index) {
final book = books[index];
if (isLoading) {
return showCircularLoader(context);
} else {
return Padding(
padding: const EdgeInsets.fromLTRB(
0, 10, 0, 0),
child: Column(
children: [
buildCitesList(book),
SizedBox10(),
Divider(),
SizedBox10()
],
),
);
//
}
}),
),
Widget buildCitesList(Book book) => ListTile(
leading: checkboxCustom(context, _selectedCities.contains(book.id),
(bool? selected) {
if (selected != null) {
setState(() {
_onCitySelected(selected, book.id);
});
}
}),
title: Text(book.title, style: GoogleFonts.montserrat()),
subtitle: Padding(
padding: const EdgeInsets.only(top: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//charges textfield
textformfieldCustomwithouticon1(
context,
TextInputType.number,
MediaQuery.of(context).size.width * 0.3,
charges, (String value) {
setState(() {
charges.text = value;
charges.selection = TextSelection.fromPosition(
TextPosition(offset: charges.text.length));
});
}, (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
}, 'Charges', 'Charges', 10.0),
SizedBox(
width: 10,
),
textformfieldCustomwithouticon1(
context,
TextInputType.number,
MediaQuery.of(context).size.width * 0.3,
time, (String value) {
setState(() {
time.text = value;
time.selection = TextSelection.fromPosition(
TextPosition(offset: time.text.length));
});
}, (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
}, 'Time', 'Time', 10.0),
// SizedBox10(),
],
),
),
);
here is the snap of output
please help how i can do this.
.addListener on chargesMaxController for chargesMax.
Here is the simplified version of your desired output.
class TimeToCode extends StatefulWidget {
TimeToCode({Key? key}) : super(key: key);
#override
_TimeToCodeState createState() => _TimeToCodeState();
}
class _TimeToCodeState extends State<TimeToCode> {
TextEditingController chargesMaxController = TextEditingController();
TextEditingController controller1 = TextEditingController();
TextEditingController controller2 = TextEditingController();
bool tile1CheckBox = false;
bool tile2CheckBox = false;
#override
void initState() {
super.initState();
chargesMaxController.addListener(() {
if (tile1CheckBox) {
setState(() {
controller1.text = chargesMaxController.text;
});
}
if (tile2CheckBox) {
setState(() {
controller2.text = chargesMaxController.text;
});
}
});
}
#override
void dispose() {
chargesMaxController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Text("chargesMax "),
TextField(
controller: chargesMaxController,
),
ListTile(
leading: Checkbox(
value: tile1CheckBox,
onChanged: (value) {
setState(() {
tile1CheckBox = value!;
});
if (value!) {
setState(() {
controller1.text = chargesMaxController
.text; // handle Ui update on checkedBOx value changes
});
}
},
),
title: TextField(
controller: controller1,
),
),
ListTile(
leading: Checkbox(
value: tile2CheckBox,
onChanged: (value) {
setState(() {
tile2CheckBox = value!;
});
if (value!) {
setState(() {
controller2.text = chargesMaxController
.text; // handle Ui update on checkedBOx value changes
});
}
},
),
title: TextField(
controller: controller2,
),
)
],
),
);
}
}
I am trying to create a dynamic form which contains some textbox and radio button. I am using RadioListTile for the same.
In the below code you can see I am using var nameController = TextEditingController(); to get the value of textbox. I am not sure what can be used for RadioListTile.
I am also struggling to show Radio Button Dynamically. I have added full code in the below. How can I get the radio button working and get the value of the selected items, so they can be saved to the database?
class Price extends StatefulWidget {
#override
_PriceState createState() => _PriceState();
}
class FruitsList {
String name;
int index;
FruitsList({this.name, this.index});
}
class _PriceState extends State<Price> {
static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final _scaffoldKey = GlobalKey<ScaffoldState>();
int currentIndex = 0;
String person;
String age;
String job;
// Default Radio Button Item
String radioItem = 'Mango';
// Group Value for Radio Button.
int id = 1;
List<FruitsList> fList = [
FruitsList(
index: 1,
name: "Mango",
),
FruitsList(
index: 2,
name: "Banana",
),
FruitsList(
index: 3,
name: "Apple",
),
FruitsList(
index: 4,
name: "Cherry",
),
];
#override
void initState() {
super.initState();
cards.add(createCard());
}
var nameTECs = <TextEditingController>[];
var ageTECs = <TextEditingController>[];
var jobTECs = <TextEditingController>[];
--- Need to help to add Controller for Radio Button ---
var cards = <Card>[];
Card createCard() {
var nameController = TextEditingController();
var ageController = TextEditingController();
var jobController = TextEditingController();
nameTECs.add(nameController);
ageTECs.add(ageController);
jobTECs.add(jobController);
return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Service ${cards.length + 1}'),
TextFormField(
style: TextStyle(color: Colors.blue),
controller: nameController,
decoration: InputDecoration(labelText: 'Name'),
validator: validatetext,
onSaved: (String val) {
person = val;
},
),
TextFormField(
style: TextStyle(color: Colors.blue),
controller: ageController,
decoration: InputDecoration(labelText: 'age'),
validator: validatetext,
onSaved: (String val) {
age = val;
},
),
TextFormField(
style: TextStyle(color: Colors.blue),
controller: jobController,
decoration: InputDecoration(labelText: 'Job'),
validator: validatetext,
onSaved: (String val) {
job = val;
},
),
//Expanded(
// child: Container(
// height: 350.0,
// child:
Row(
children:
fList.map((data) => RadioListTile(
title: Text("${data.name}"),
groupValue: id,
value: data.index,
onChanged: (val) {
setState(() {
radioItem = data.name ;
id = data.index;
});
},
)).toList(),
),
//)),
/* CheckboxListTile(
title: Text("title text"),
value: checkedValue,
onChanged: (newValue) {
setState(() {
checkedValue = newValue;
});
},
//onChanged: (newValue) { ... },
controlAffinity: ListTileControlAffinity.leading, // <-- leading Checkbox
), */
SizedBox(height: 10),
],
),
// ),
);
}
void _validateInputs() {
print('button');
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
_onDone();
} else {
}
}
_onDone() {
updateProfile();
List<PersonEntry> entries = [];
for (int i = 0; i < cards.length; i++) {
var name = nameTECs[i].text;
var age = ageTECs[i].text;
var job = jobTECs[i].text;
entries.add(PersonEntry(name, age, job));
}
}
///////// Save to DB ////////////////////
Future updateProfile() async{
try{
for (int i = 0; i < cards.length; i++) {
var name = nameTECs[i].text;
var age = ageTECs[i].text;
var job = jobTECs[i].text;
Map<String, dynamic> body = {'name': name, 'age': age, 'job' : job };
print(body);
nameTECs[i].clear();
//if(rang == true){
Response response =
await Dio().post("http://192.168.1.102:8080/adddetails.php", data: body);
print(response.statusCode);
if(response.statusCode == 404){
print('404');
}
if(response.statusCode == 200){
nameTECs[i].clear();
}
}
} catch (e) {
print("Exception Caught: $e");
}
}
///////////////////////////////
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: myAppBar(),
endDrawer: myDrawer(),
body: Column(
children: <Widget>[
Expanded(
child:new Form(
key: _formKey,
child: ListView.builder(
itemCount: cards.length,
itemBuilder: (BuildContext context, int index) {
return cards[index];
},
),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 2.0),
color: Colors.grey,
child:Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Container(
Padding(
padding: const EdgeInsets.all(16.0),
child: FloatingActionButton(
heroTag: "btn1",
child: Icon(Icons.add),
onPressed: () => setState(() => cards.add(createCard())),
backgroundColor: Colors.green,
)
/*RaisedButton(
child: Text('Add new'),
onPressed: () => setState(() => cards.add(createCard())),
),*/
),
Padding(
padding: const EdgeInsets.all(16.0),
child: FloatingActionButton(
heroTag: "btn2",
child: Icon(Icons.remove), onPressed: () => setState(() => cards.removeLast()),
backgroundColor: Colors.red,
)
),
Padding(
padding: const EdgeInsets.all(16.0),
child: FloatingActionButton(
heroTag: "btn3",
child: Icon(Icons.save), onPressed: _validateInputs),
)
],
),
),
],
),
);
}
);
}
}
class PersonEntry {
final String name;
final String age;
final String studyJob;
PersonEntry(this.name, this.age, this.studyJob);
#override
String toString() {
return 'Person: name= $name, age= $age, study job= $studyJob';
}
}
Size get preferredSize => Size.fromHeight(kToolbarHeight);
String validatetext(String value) {
if (value.length < 5)
return 'More than 5 char is required';
else
return null;
}
Update
I want to show Radio buttons that user can select and once user submit the form I can get those value for http request. As you can I have added options to add or remove cards. So, these radio buttons will also generated.
Create field int _selectedRadioIndex
and change code
fList.map((data) => RadioListTile(
title: Text("${data.name}"),
groupValue: id,
value: data.index,
onChanged: (val) {
setState(() {
radioItem = data.name ;
id = data.index;
});
},
)).toList(),
to
fList.map((data) => RadioListTile(
title: Text("${data.name}"),
groupValue: id,
value: data.index,
onChanged: (val) {
setState(() {
radioItem = data.name ;
id = data.index;
_selectedRadioIndex = val;
});
},
)).toList(),
then in code just get it fList.firstWhere((element) => element.index == _selectedRadioIndex)
I'm trying to build an app in flutter in which during quiz, I'm using radio buttons. I want to highlight the correct answer and the answer selected by the user if the correct answer is not selected by the user.
If the correct answer is selected then I just want to select the user selected answer.
I cannot find any way to do it.
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
widget.content.getQuestion(),
style: Constants.articleQuestionStyle,
),
Container(),
Column(
children: widget.content
.getOptions()
.map<Widget>((value) => _buildRadioBtn(value))
.toList()),
//
// {
// return Row(children: [
// Radio(
// value: value,
// groupValue: widget.content.getGuess(),
// onChanged: (val){
// print("value: ${value}");
// print("isChecked: ${widget.content.isChecked()}");
// return //_buildRadioBtn(val);
//// widget.content.isChecked()
//// ? null :
// _buildRadioBtn(val);//_handleValueChanged(val);
// },
// activeColor: (widget.content.getGuess() == widget.content.getCorrectAnswer())? Colors.orange: Colors.red,
// ),
//
// Text(
// value,
// style: Constants.articleBodyTextStyle,
// )
// ]);
// }
// ).toList()),
and
_buildRadioBtn(value) {
// bool isCorrect = widget.content.getCorrectAnswer().contains(value);
// bool isChosen = widget.content.getGuess().contains(value);
return Row(
children: <Widget>[
Radio(
value: widget.content.isChecked(),
groupValue: widget.content.getGuess(),
onChanged: (value){
if(!widget.content.isChecked()) {
// print("ffffff");
// widget.content.registerGuess(value);
// print("abc");
// setState(() {});
_handleValueChanged(value);
}
},
activeColor: (
widget.content.getGuess() == widget.content.getCorrectAnswer())? Colors.orange: Colors.red,
),
Text(
// "hello",
value,
style: Constants.articleBodyTextStyle,
)
],
);
}
}
The way I think it will work is to rebuild the radio button once the user selects the answer, but I cannot do so. Please help.
Method: 1
String question = 'Q 1', answer = 'A 3', defaultValue = 'nil';
List<String> options = ['A 1', 'A 2', 'A 3', 'A 4'], info = ['', '', '', ''];
List<Color> bgs = [Colors.white, Colors.white, Colors.white, Colors.white];
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: <Widget>[
ListTile(title: Text(question)),
ListView.builder(
shrinkWrap: true,
itemCount: options.length,
itemBuilder: (cc, ii) {
return Card(
color: bgs[ii],
child: ListTile(
title: Text(options[ii]),
subtitle: Text(info[ii]),
leading: Radio(
value: options[ii],
groupValue: defaultValue,
onChanged: (String value) {
setState(() {
defaultValue = value;
});
},
),
),
);
}),
RaisedButton(
onPressed: () {
if (defaultValue == answer) {
setState(() {
int ind = options.indexOf(defaultValue);
bgs[ind] = Colors.green;
info[ind] = 'Correct Answer !';
});
} else {
setState(() {
int wrongInd = options.indexOf(defaultValue);
bgs[wrongInd] = Colors.redAccent;
info[wrongInd] = 'Wrong Answer !';
int correctInd = options.indexOf(answer);
bgs[correctInd] = Colors.green;
info[correctInd] = 'Correct Answer !';
});
}
},
child: Text('Submit'))
],
),
),
);
}
Method: 2
String question = 'Q 1', answer = 'A 3', defaultValue = 'nil';
List<String> options = ['A 1', 'A 2', 'A 3', 'A 4'], info = ['', '', '', ''],radioValues=[];
List<Color> bgs = [Colors.black, Colors.black, Colors.black, Colors.black];
#override
void initState(){
super.initState();
radioValues.addAll(options);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: <Widget>[
ListTile(title: Text(question)),
ListView.builder(
shrinkWrap: true,
itemCount: options.length,
itemBuilder: (cc, ii) {
return ListTile(
title: Text(options[ii],
style:TextStyle(color:bgs[ii])),
subtitle: Text(info[ii],
style:TextStyle(color:bgs[ii])),
leading: Radio(
value: radioValues[ii],
groupValue: defaultValue,
onChanged: (String value) {
setState(() {
defaultValue = value;
});
},
),
);
}),
RaisedButton(
onPressed: () {
if (defaultValue == answer) {
setState(() {
int ind = options.indexOf(defaultValue);
bgs[ind] = Colors.green;
info[ind] = 'Correct Answer !';
});
} else {
setState(() {
int wrongInd = options.indexOf(defaultValue);
bgs[wrongInd] = Colors.redAccent;
info[wrongInd] = 'Wrong Answer !';
int correctInd = options.indexOf(answer);
bgs[correctInd] = Colors.green;
info[correctInd] = 'Correct Answer !';
radioValues[wrongInd] = defaultValue;
radioValues[correctInd] = defaultValue;
});
}
},
child: Text('Submit'))
],
),
),
);
}
I suggest that you leave the select state of what the user picked. What you can do is change the colors or styling of the text of the items to reflect which the user picked vs which is the correct answer.