Reputation: 23
I have a StatefulWidget
that contains a ListView()
with a ListView.builder()
. The ListView.builder()
builds several CheckboxListTile()
's using two Lists, one for the labels and one for the booleans. However it is not working as intended. I tried to create a minimal example of the problem, it looks like this:
import 'package:flutter/material.dart';
class TestClass extends StatefulWidget {
@override
_TestClassState createState() => _TestClassState();
}
class _TestClassState extends State<TestClass> {
@override
Widget build(BuildContext context) {
bool firstBoolean = false;
bool secondBoolean = false;
List labels = [
"First Checkbox",
"Second Checkbox",
];
List<bool> booleans = [
firstBoolean,
secondBoolean,
];
return ListView(
children: [
Container(
height: MediaQuery.of(context).size.height * 0.5,
child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: 2,
itemBuilder: (context, index) {
return CheckboxListTile(
title: Text("${labels[index]}"),
value: booleans[index],
onChanged: (bool newValue) {
setState(() {
secondBoolean = newValue;
print(
"firstBoolean, secondBoolean: [$firstBoolean, $secondBoolean]");
});
},
);
}),
),
],
);
}
}
This does not change the checkboxes and also not the assigned booleans. If I replace this line:
value: booleans[index]
with
value: firstBoolean
And move the bool firstBoolean = false;
above the @override
in the _TestCassState
It 'works' but since all the checkboxes now have the same boolean variable assigned to them it changes all of them at the same time. But I dont understand why this 'works' and the code above does not. Also if I try to create all the booleans I need above @override
and then try to create a list containing all of them, I get this error:
The instance member 'firstBoolean' can't be accessed in an initializer. Try replacing the reference to the instance member with a different expression
If I move to List with the labels above the @override
it doesnt give me this error and assigns the labels correctly
I feel like I am not understanding something fundamental about StatefulWidgets. So I would really aprecciate if somebody could give me an explanation of how to solve this correctly and why this is not working as intended
Upvotes: 2
Views: 5601
Reputation: 107121
You are declaring the variables in the wrong place, you have initialised those inside the build function. So whenever you call setState the variable will be re-initialised to the default values. Also you don't need to use firstBoolean
and secondBoolean
. Change your code to:
class TestClass extends StatefulWidget {
@override
_TestClassState createState() => _TestClassState();
}
class _TestClassState extends State<TestClass> {
List labels = [
"First Checkbox",
"Second Checkbox",
];
List<bool> booleans = List.filled(2, false); // Initialising with default value
@override
Widget build(BuildContext context) {
return ListView(
children: [
Container(
height: MediaQuery.of(context).size.height * 0.5,
child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: 2,
itemBuilder: (context, index) {
return CheckboxListTile(
title: Text("${labels[index]}"),
value: booleans[index],
onChanged: (bool newValue) {
setState(() {
booleans[index] = newValue;
print(booleans[index]);
});
},
);
},
),
),
],
);
}
}
Upvotes: 0
Reputation: 45
Whenever you call the setState() method the build function gets called and initialises the variables with the same old value . The simplest solution to your problem is to keep the variables out of build function so that they don't get initialised everytime you call setState() method . Here's what you should do
import 'package:flutter/material.dart';
class TestClass extends StatefulWidget {
@override
_TestClassState createState() => _TestClassState();
}
class _TestClassState extends State<TestClass> {
// Declare the variables above build fucntion
bool firstBoolean = false;
bool secondBoolean = false;
List labels = [
"First Checkbox",
"Second Checkbox",
];
List<bool> booleans = [false,false];
@override
Widget build(BuildContext context) {
return ListView(
children: [
Container(
height: MediaQuery.of(context).size.height * 0.5,
child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: 2,
itemBuilder: (context, index) {
return CheckboxListTile(
title: Text("${labels[index]}"),
value: booleans[index],
onChanged: (bool newValue) {
setState(() {
secondBoolean = newValue;
print(
"firstBoolean, secondBoolean: [$firstBoolean, $secondBoolean]");
});
},
);
}),
),
],
);
}
}
Upvotes: 0
Reputation: 6405
You don't even need the individual bool variables.
You can basically have your labels
inside your TestClass
since they are not part of the state since they never change.
Next, you can have just the List<bool> booleans
in your State
class since all your changing stuff is just these booleans
itself.
So your structure will be like this now.
class TestClass extends StatefulWidget {
final List labels = [
"First Checkbox",
"Second Checkbox",
];
@override
_TestClassState createState() => _TestClassState();
}
This is good practice to put them in the StatefulWidget
class. You can then use them inside your State
class like this - widget.labels[index]
.
So, your State
class will be,
class _TestClassState extends State<TestClass> {
List<bool> booleans = [false,false];
@override
Widget build(BuildContext context) {
return ListView(
children: [
Container(
height: MediaQuery.of(context).size.height * 0.5,
child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: 2,
itemBuilder: (context, index) {
return CheckboxListTile(
title: Text("${widget.labels[index]}"),
value: booleans[index],
onChanged: (bool newValue) {
setState(() {
// There was a logic error here in your code, so changed it to work correctly
booleans[index] = newValue;
});
},
);
}),
),
],
);
}
}
Upvotes: 2
Reputation: 1140
when ever you call a setstate()
then the build method is invoked, and you are declared in the build method.
override the initState()
method of state.
class _TestClassState extends State<TestClass> {
bool firstBoolean,secondBoolean;
List labels;
List<bool> booleans;
@override
void initState(){
super.initState();
firstBoolean = false;
secondBoolean = false;
labels = [
"First Checkbox",
"Second Checkbox",
];
booleans = [
firstBoolean,
secondBoolean,
];
}
}
and remove the variable declarations from build method
Upvotes: 0