Change checkbox state when clicking on another widget - flutter

I have a goal to change the state of the checkbox when clicking on the adjacent widget. I tried to create a stateful function but unfortunately my code doesn't work.
Here is the function -
void _changeFlagCheckBox(bool? value){
setState(() {
MyCheckBoxCountry.isChecked = value!;
});
}
Inkwell - when you click on it, it should change the state of myCheckBoxCountry.
child: ListView.builder(
itemCount: city.length,
shrinkWrap: true
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
print(city[index]);
_changeFlagCheckBox;
},
child: Container(
margin: const EdgeInsets.only(top: 15),
child:Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children:[
Row(
children:[
SizeBox(width: 10,),
Text(city[index], style: TextStyle(color: ConfigColor.white, fontWeight: FontWeight.w500, fontSize: 15),)
],
),
MyCheckBoxCountry()
],
)
),
);
},
),
myCheckBoxCountry -
class MyCheckBoxCountry extends StatefulWidget {
static bool isChecked = false;
#override
_MyCheckBoxState createState() => _MyCheckBoxState();
}
class _MyCheckBoxState extends State<MyCheckBoxCountry> {
#override
Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(
unselectedWidgetColor: ConfigColor.grey,
),
child: Checkbox(
activeColor: ConfigColor.green,
checkColor: ConfigColor.background,
value: MyCheckBoxCountry.isChecked,
shape: CircleBorder(),
onChanged: (bool? value) {
setState(() {
MyCheckBoxCountry.isChecked = value!;
});
},
),
);
}
}

First of all you're not sending any value in your _changeFlagCheckBox function in your Inkwell so how is it supposed to change :
InkWell(
onTap: () {
print(city[index]);
_changeFlagCheckBox; //should be _changeFlagCheckBox(!MyCheckBoxCountry.isChecked)
},
but to understand why didn't you get an error like that, well that's because you made the bool value nullable in your _changeFlagCheckBox function:
void _changeFlagCheckBox(bool? value){ //should be (bool value)
setState(() {
MyCheckBoxCountry.isChecked = value!;
});
}
Though even then you are not using your isChecked value in your MyCheckBoxCountry class anywhere :
Checkbox(
activeColor: ConfigColor.green,
checkColor: ConfigColor.background,
value: MyCheckBoxCountry.isChecked,
shape: CircleBorder(),
onChanged: (bool? value) {
setState(() {
MyCheckBoxCountry.isChecked = value!; //should be the other way around
});
},
),
Should have been :
setState(() {
value = MyCheckBoxCountry.isChecked;
});

I guess since it is an another stateful widget class, It does not recognize the setState. Calling child widget from parent widget would solve the problem.

Related

Flutter DropdownButton doesn't allow me to select a different value from the drop down list

Here's my code
DropdownButton<int>(
value: map['completedVersion'].toInt(), //selected
elevation: 16,
style: TextStyle(color: Theme.of(context).accentColor),
underline: Container(
height: 2,
color: Colors.blueAccent,
),
onChanged: (int? newValue) {
versionInput.text = newValue.toString();
},
items: [for (var i = map['completedVersion'].toInt() as int; i <= map['requiredVersion']; i++) i]
.map<DropdownMenuItem<int>>((int value) {
return DropdownMenuItem<int>(
value: value,
child: Text(value.toString()),
);
}).toList(),
)
Class level declaration
TextEditingController versionInput = TextEditingController();
#override
void initState() {
versionInput.text = map['completedVersion'].toString(); //set the initial value of text field
super.initState();
}
Here's the behavior
It doesn't let me select any other value (eg: 4,5,6). I do see that he onChanged() method is hit when I put in a break point. But I'm not sure why the selection goes back to the original value.
TextEditingValue
class Example extends StatelessWidget {
Example({Key? key}) : super(key: key);
TextEditingController versionInput = TextEditingController(text: "2");
#override
Widget build(BuildContext context) {
return Column(
children: [
ValueListenableBuilder(
valueListenable: versionInput,
builder: (BuildContext context, TextEditingValue selectedValue, Widget? child) {
return DropdownButton<int>(
value: int.parse(selectedValue.text),
elevation: 16,
style: TextStyle(color: Theme.of(context).accentColor),
underline: Container(
height: 2,
color: Colors.blueAccent,
),
onChanged: (value) {},
items: [for (var i = 0 as int; i <= 6; i++) i].map<DropdownMenuItem<int>>((int value) {
return DropdownMenuItem<int>(
value: value,
child: Text(value.toString()),
onTap: () {
versionInput.text = value.toString();
},
);
}).toList(),
);
},
),
],
);
}
}
If you really use the TextEditingController without a text field and you don't need it, you can use it directly in the ValueNotifier.
ValueNotifier
class Example extends StatelessWidget {
Example({Key? key}) : super(key: key);
ValueNotifier<int> versionInput = ValueNotifier<int>(2); // initialValue
#override
Widget build(BuildContext context) {
return Column(
children: [
ValueListenableBuilder(
valueListenable: versionInput,
builder: (BuildContext context, int selectedValue, Widget? child) {
return DropdownButton<int>(
value: selectedValue,
elevation: 16,
style: TextStyle(color: Theme.of(context).accentColor),
underline: Container(
height: 2,
color: Colors.blueAccent,
),
onChanged: (value) {},
items: [for (var i = 0 as int; i <= 6; i++) i].map<DropdownMenuItem<int>>((int value) {
return DropdownMenuItem<int>(
value: value,
child: Text(value.toString()),
onTap: () {
versionInput.value = value;
},
);
}).toList(),
);
},
),
],
);
}
}
You have to change two things :
1- value: map['completedVersion'].toInt(), ==> value: versionInput.text,
2-
onChanged: (newValue) {
setState(() {
versionInput.text = newValue.toString();
});
},
this should work:
#override
Widget build(BuildContext context) {
return Column(
children: [
ValueListenableBuilder(
valueListenable: versionInput,
builder: (BuildContext context, TextEditingValue selectedValue, Widget? child) {
return DropdownButton<int>(
hint: Text(
int.parse(selectedValue.text) ?? '3'
),
elevation: 16,
style: TextStyle(color: Theme.of(context).accentColor),
underline: Container(
height: 2,
color: Colors.blueAccent,
),
onChanged: (int? newValue) {
if(newValue != null){
setState(()=> versionInput.text = newValue.toString());
}
},
items: [for (var i = 0; i <= 6; i++) i].map<DropdownMenuItem<int>>((int value) {
return DropdownMenuItem<int>(
value: value,
child: Text(value.toString()),
onTap: () {
versionInput.text = value.toString();
},
);
}).toList(),
);
},
),
],
);
}
}

Change elevated button color via radio buttons in flutter

I want to change the Elevated button color when I press the radio buttons. 1.button -> red, 2.button -> yellow, 3.button -> green. In the Elevated.stylefrom if condition did not work. Just ternary condition works but it is only for one condition. I want to add 3 conditions. I hope it was clear.
Or do I need to create a model for this?
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:task_shopping_app/const/const.dart';
import 'package:task_shopping_app/screens/city_input_screen.dart';
import 'package:task_shopping_app/screens/weather.dart';
class RadioButton extends StatefulWidget {
#override
_RadioButtonState createState() => _RadioButtonState();
}
class _RadioButtonState extends State<RadioButton> {
int selectedValue = 0;
final enteredCityInfo = TextEditingController();
String name = '';
// if selectedValue and group value matches then radio button will be selected.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
ButtonBar(
alignment: MainAxisAlignment.center,
children: <Widget>[
Radio<int>(
value: 1,
groupValue: selectedValue,
onChanged: (value) => setState(() {
selectedValue = value!;
print(selectedValue);
})),
Radio<int>(
value: 2,
groupValue: selectedValue,
onChanged: (value) => setState(() {
selectedValue = value!;
print(selectedValue);
})),
Radio<int>(
value: 3,
groupValue: selectedValue,
onChanged: (value) => setState(() {
selectedValue = value!;
print(selectedValue);
})),
],
),
Padding(
padding: const EdgeInsets.all(25.0),
child: TextField(
controller: enteredCityInfo,
),
),
ElevatedButton(
onPressed: () {
setState(() {
name = enteredCityInfo.text;
//Here we want to see user entry for the text field area in the debug console.
print(name);
// Get.to(() => WeatherScreen());
});
},
child: Text('Create'),
style: ElevatedButton.styleFrom(
primary: selectedValue == 1 ? Colors.red : Colors.yellow,
),
)
],
),
);
}
}
enter code here
I think you can create a function to convert the value into Colors
Color valueToColor(int value) {
switch (value) {
case 1:
return Colors.red;
case 2:
return Colors.yellow;
case 3:
return Colors.green;
default:
/// return whatever you like to deal with some exceptional case
}
}
And make the style parameter of ElevatedButton like following
style: ElevatedButton.styleFrom(
primary: valueToColor(selectedValue),
),
Use like this for multiple conditions:
primary:(selectedValue == 1) ? Colors.red : (selectedValue == 2)?
Colors.yellow:Colors.green,

Is there any way I can reset the dynamic fields I added into a form to their previous state if user doesn't make any changes (Presses back)?

I'm trying to create a dynamic form so I used the idea of using a listview builder to create it. I was able to successfully create it but I faced that I cannot discard changes made to the form by popping it off after editing it. The two textFormField Job name and rate per hour were able to discard changes as they were using onsaved but on the checkbox I can't do that as it has onChanged which wraps setstate to change its state.
You can take a look at the video at this link to see how it functions as of now - https://vimeo.com/523847256
As you can see that it is retaining the data even after popping the page and coming back which I don't want it to. I'm looking for a way to prevent that and make the form the same as before if the user didn't press save.
I have tried to reassign the variables() in onpressed of back button but that didn't work. I also tried push replacement to the same page to reset it but that also didn't work. I think the cuprit here is the sublist and the initialValueTextFormField and initialValueCheckbox which are used declared under ListView.builder but I don't know how to fix that without affecting the dynamic list functionality.
class EditJobPage extends StatefulWidget {
const EditJobPage({Key key, this.job}) : super(key: key);
final Job job;
static Future<void> show(BuildContext context, {Job job}) async {
await Navigator.of(context, rootNavigator: true).pushNamed(
AppRoutes.editJobPage,
arguments: job,
);
}
#override
_EditJobPageState createState() => _EditJobPageState();
}
class _EditJobPageState extends State<EditJobPage> {
final _formKey = GlobalKey<FormState>();
String _name;
int _ratePerHour;
List<dynamic> _subList = [];
Set newSet = Set('', false);
#override
void initState() {
super.initState();
if (widget.job != null) {
_name = widget.job?.name;
_ratePerHour = widget.job?.ratePerHour;
_subList = widget.job?.subList;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 2.0,
title: Text(widget.job == null ? 'New Job' : 'Edit Job'),
leading: IconButton(
icon: Icon(Icons.clear),
onPressed: () {
Navigator.of(context).pop();
},
),
actions: <Widget>[
FlatButton(
child: const Text(
'Save',
style: TextStyle(fontSize: 18, color: Colors.white),
),
onPressed: () => _submit(),
),
],
),
body: _buildContents(),
backgroundColor: Colors.grey[200],
);
}
Widget _buildContents() {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: _buildForm(),
),
),
),
);
}
Widget _buildForm() {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildFormChildren(),
),
);
}
List<Widget> _buildFormChildren() {
print(_subList);
return [
TextFormField(
decoration: const InputDecoration(labelText: 'Job name'),
keyboardAppearance: Brightness.light,
initialValue: _name,
validator: (value) =>
(value ?? '').isNotEmpty ? null : 'Name can\'t be empty',
onChanged: (value) {
setState(() {
_name = value;
});
},
),
TextFormField(
decoration: const InputDecoration(labelText: 'Rate per hour'),
keyboardAppearance: Brightness.light,
initialValue: _ratePerHour != null ? '$_ratePerHour' : null,
keyboardType: const TextInputType.numberWithOptions(
signed: false,
decimal: false,
),
onChanged: (value) {
setState(() {
_ratePerHour = int.tryParse(value ?? '') ?? 0;
});
},
),
Column(
children: <Widget>[
ListView.builder(
shrinkWrap: true,
itemCount: _subList?.length ?? 0,
itemBuilder: (context, index) {
String initialValueTextFormField =
_subList[index].subListTitle.toString();
bool initialValueCheckbox = _subList[index].subListStatus;
return Row(
children: [
Checkbox(
value: initialValueCheckbox,
onChanged: (bool newValue) {
setState(
() {
initialValueCheckbox = newValue;
_subList.removeAt(index);
_subList.insert(
index,
Set(initialValueTextFormField,
initialValueCheckbox));
},
);
},
),
Expanded(
child: TextFormField(
minLines: 1,
maxLines: 1,
initialValue: initialValueTextFormField,
autofocus: false,
textAlign: TextAlign.left,
onChanged: (title) {
setState(() {
initialValueTextFormField = title;
_subList.removeAt(index);
_subList.insert(
index,
Set(initialValueTextFormField,
initialValueCheckbox));
});
},
decoration: InputDecoration(
border: UnderlineInputBorder(),
labelStyle: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
),
filled: true,
hintText: 'Write sub List here',
),
),
),
],
);
},
),
TextButton(
onPressed: () {
setState(() {
_subList.add(newSet);
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.add),
Text('Add Sub Lists'),
],
),
),
],
),
];
}
void _submit() {
final isValid = _formKey.currentState.validate();
if (!isValid) {
return;
} else {
final database = context.read<FirestoreDatabase>(databaseProvider);
final id = widget.job?.id ?? documentIdFromCurrentDate();
final job = Job(
id: id,
name: _name ?? '',
ratePerHour: _ratePerHour ?? 0,
subList: _subList);
database.setJob(job);
Navigator.of(context).pop();
}
}
}
And this is the link to the full repository of the whole flutter app in case you want to look at any other part:- https://github.com/brightseagit/dynamic_forms . Thank you.
Note - This is the edited code of this repo - https://github.com/bizz84/starter_architecture_flutter_firebase.
When assigning the list we need to use _subList = List.from(widget.job.subList) instead of _subList = widget.job.subList.
Otherwise, the changes made in _subList will also be made in job.subList .

I am trying to create list of TextFormFields which takes numbers as inputs and I want to Sum all those numbers

I am trying to create list of TextFormFields which takes numbers as inputs and I want to Sum all those numbers. When I click on a button on app bar new textformfield appears and user enters value..validator is also working fine...But I am not able to do the Sum. When I used print in Onsaved method it displays all the entered values..If I use Controller, whatever the text we enter in formfield it is displaying same same in all the other textfields also..so controller is not working...I created TextFormField in different function and calling that function when button is pressed. I created another button to go to next screen at the same time to validate which works fine...
Below is the TextFormField code: Please help to Sum all the values entered in it:
child: TextFormField(
// controller: _childController,
decoration: InputDecoration(
hintText: 'Value $_count',
border: InputBorder.none,
contentPadding: EdgeInsets.only(top: 5, left: 20)),
keyboardType: TextInputType.number,
style: TextStyle(
color: Color.fromARGB(255, 0, 0, 0),
fontWeight: FontWeight.w400,
fontSize: 24,
),
validator: (String value) {
double sal = double.tryParse(value);
if (sal == null) {
return 'enter or delete row';
}
},
onSaved: (String value) {
// print(_childController.text);
// print(value);
_mVal = value;
double _mVal2 = double.tryParse(_mVal);
double _mVal3;
print(_mVal);
int k = 0;
_children.forEach((element) {
int y = int.tryParse(_mVal);
k=k+y;
print(k);
}
Here is a quick example of how you can achieve this:
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Test(),
),
);
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
final _formKey = GlobalKey<FormState>();
List<TextEditingController> textFieldControllers = [];
int numberOfTextFields = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
addNewTextField();
},
),
body: Stack(
children: [
Form(
key: _formKey,
child: ListView.builder(
itemCount: numberOfTextFields,
itemBuilder: (BuildContext context, int index) {
return TextFormField(
validator: (String value) {
double sal = double.tryParse(value);
if (sal == null) {
return 'enter or delete row';
}
return null;
},
controller: textFieldControllers[index],
);
},
),
),
Align(
alignment: Alignment.bottomCenter,
child: TextButton(
onPressed: () {
if (_formKey.currentState.validate()) {
showDialog(
context: context,
builder: (BuildContext context) {
return Center(
child: Material(
child: Container(
padding: EdgeInsets.all(10.0),
child: Text(
'The sum is ${textFieldControllers.fold(0, (previousValue, element) => previousValue + int.parse(element.value.text))}'),
),
),
);
});
}
},
child: Container(
padding: EdgeInsets.all(10.0),
color: Colors.redAccent,
child: Text('Tap to sum'),
),
),
),
],
),
);
}
void addNewTextField() {
textFieldControllers.add(TextEditingController());
numberOfTextFields++;
setState(() {});
}
#override
void dispose() {
textFieldControllers.forEach((textFieldController) => textFieldController.dispose());
super.dispose();
}
}
You can expand on this idea to remove textField if needed. Just don't forget to dispose your textFields.
How does this work: Each time a TextField Widget is create, an associated TextEditingController is created and given to the TextField. When we want to sum, we just iterate on the TextEditingController list.

onPressed create multiple instances of CheckboxListTile in Flutter

I'm new to flutter so I'm not sure how to go about this. I want to be able to add another instance of the same CheckboxListTile when a user presses a button right below the most recent CheckboxListTile.
The code for how it currently is is below.
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
CheckboxListTile(
title: TextField(
autocorrect: true,
),
value: checkBoxValue,
secondary: Icon(Icons.assignment),
onChanged: (bool newValue) {
setState(() {
checkBoxValue = newValue;
});
}
),
],
The code of an example of how I would want the app to appear after a user presses is below.
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
CheckboxListTile(
title: TextField(
autocorrect: true,
),
value: checkBoxValue,
secondary: Icon(Icons.assignment),
onChanged: (bool newValue) {
setState(() {
checkBoxValue = newValue;
});
}
),
CheckboxListTile(
title: TextField(
autocorrect: true,
),
value: checkBoxValue,
secondary: Icon(Icons.assignment),
onChanged: (bool newValue) {
setState(() {
checkBoxValue = newValue;
});
}
),
],
Thanks in advance!
Generating widgets in a ListView is by adding List<Widget> in its children. Simply put, it roughly looks similar to.
ListView(
children: <Widget>[widgetA, widgetA, widgetC]);
What you need to do is to manually add widgets in List<Widget>. You can create a List<Widget> _checkboxList and create a function that returns a CheckboxListTile widget.
CheckboxListTile _checkboxListTile(){
return CheckboxListTile(
title: TextField(
autocorrect: true,
),
value: checkBoxValue,
secondary: Icon(Icons.assignment),
onChanged: (bool newValue) {
setState(() {
checkBoxValue = newValue;
});
}
);
}
and on your button, call _checkboxList.add(_checkboxListTile()); to add it on List<Widget> _checkboxList
RaisedButton(
onPressed: () {
setState(() {
_checkboxList.add(_checkboxListTile());
});
},
child: Text('Add checkbox'),
),
To display List _checkboxList on your screen:
Widget build(BuildContext context) {
return ListView(children: _checkboxList);
}
Let me know if this helps.
One way you could do it is by using a ListView.builder like this..
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
List<bool> checkBoxesCheckedStates = [false];
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: checkBoxesCheckedStates.length,
itemBuilder: (BuildContext context, int index){
return CheckboxListTile(
title: TextField(
autocorrect: true,
),
value: checkBoxesCheckedStates[index],
secondary: Icon(Icons.assignment),
onChanged: (bool newValue) {
setState(() {
checkBoxesCheckedStates[index] = newValue;
});
},
);
},
),
),
RaisedButton(
child: Text('Add'),
onPressed: (){
setState(() {
checkBoxesCheckedStates.add(false);
});
},
),
],
);
}
}