I have a list of checkboxes as shown in the image below. However, when I click the checkbox to check/uncheck, nothing happens. I have to click the apply button, then reopen the filter modal for it to update. Can anyone help me to find out why it wont update immediately after I press the checkbox? any help would be appreciated.
Source Code:
class DashboardPage extends StatefulWidget {
const DashboardPage({Key? key}) : super(key: key);
@override
State<DashboardPage> createState() => _DashboardPageState();
}
class _DashboardPageState extends State<DashboardPage> {
List<Map<String, dynamic>> jsonData = [];
List<bool> selectedItems = [];
bool checkVal = false;
@override
void initState() {
super.initState();
loadJsonData();
initializeSelectedItems();
}
void initializeSelectedItems() {
// Only initialize selectedItems if it's empty
if (selectedItems.isEmpty) {
selectedItems = List<bool>.generate(jsonData.length, (index) => false);
}
}
void loadJsonData() {
// Replace this with your actual JSON data loading logic
jsonData = [
{
"name": "K1",
"totalSST": 54,
"sstInService": 52,
"sstOutOfService": 2,
"cashLevelNormal": 48,
"lowCashOutOfCash": 6,
},
{
"name": "K2",
"totalSST": 46,
"sstInService": 42,
"sstOutOfService": 4,
"cashLevelNormal": 45,
"lowCashOutOfCash": 1,
},
{
"name": "K3",
"totalSST": 34,
"sstInService": 32,
"sstOutOfService": 2,
"cashLevelNormal": 30,
"lowCashOutOfCash": 4,
},
{
"name": "K4",
"totalSST": 48,
"sstInService": 45,
"sstOutOfService": 3,
"cashLevelNormal": 46,
"lowCashOutOfCash": 2,
},
{
"name": "K5",
"totalSST": 36,
"sstInService": 32,
"sstOutOfService": 4,
"cashLevelNormal": 30,
"lowCashOutOfCash": 6,
},
{
"name": "K6",
"totalSST": 58,
"sstInService": 54,
"sstOutOfService": 4,
"cashLevelNormal": 56,
"lowCashOutOfCash": 2,
},
{
"name": "K7",
"totalSST": 24,
"sstInService": 24,
"sstOutOfService": 0,
"cashLevelNormal": 22,
"lowCashOutOfCash": 2,
},
{
"name": "K8",
"totalSST": 64,
"sstInService": 63,
"sstOutOfService": 1,
"cashLevelNormal": 58,
"lowCashOutOfCash": 6,
},
{
"name": "K9",
"totalSST": 50,
"sstInService": 45,
"sstOutOfService": 5,
"cashLevelNormal": 49,
"lowCashOutOfCash": 1,
},
];
initializeSelectedItems();
}
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
backgroundColor: const Color(0xFFF6F9FE),
body: SafeArea(
child: Column(
children: [
Row(
children: [
// Dashboard Container
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border(
bottom: BorderSide(
color: Color(0xFFCDCDCF),
width: 1.0,
),
),
),
child: Padding(
padding: const EdgeInsets.only(left: 26.0, right: 26.0, bottom: 15.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Text(
"Dashboard",
style: GoogleFonts.poppins(
fontSize: 28.0,
fontWeight: FontWeight.w700,
),
),
),
Padding(
padding: const EdgeInsets.only(top: 5.0),
child: Text(
"Hello, Danish Ghazi!",
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
fontSize: 14.0,
fontWeight: FontWeight.w400,
color: Color(0xFF949494),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 0.0),
child: Text(
"HQ Executive",
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
fontSize: 14.0,
fontWeight: FontWeight.w400,
color: Color(0xFF949494),
),
),
),
],
),
Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Image(
image: AssetImage('assets/Profile_Picture.png'),
),
),
],
),
],
),
),
),
),
],
),
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Container(
height: 50,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 5,
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
25,
),
border: Border.all(
color: Color(0xFFCDCDCF),
width: 1.0,
),
color: Colors.white,
),
child: TabBar(
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(
25,
),
color: Color(0xFF3CADD9),
border: Border.all(
color: Color(0xFF2D8EB4),
width: 1.0,
),
),
tabs: const [
Tab(
text: 'List View',
),
Tab(
text: 'Summary View',
),
],
),
),
),
),
//List View Content
Expanded(
child: TabBarView(
children: [
Padding(
padding: EdgeInsets.only(top: 10.0, left: 15.0, right: 15.0, bottom: 0.0),
child: RefreshIndicator(
onRefresh: _handleRefresh,
child: SingleChildScrollView(
child: Column(
children: [
//Search Input
Padding(
padding: EdgeInsets.only(top: 10.0),
child: TextFormField(
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
suffixIcon: Container(
padding: const EdgeInsets.all(10.0),
child: const Icon(Icons.search, color: Color(0xFFADADAD)),
),
hintText: 'Search Keyword',
hintStyle: GoogleFonts.poppins(
fontSize: 16.0,
fontWeight: FontWeight.w200,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
borderSide: const BorderSide(
color: Color(0xFFCDCDCF),
width: 2.0,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
borderSide: const BorderSide(
color: Color(0xFFCDCDCF),
width: 2.0,
),
),
contentPadding: const EdgeInsets.all(10.0),
),
),
),
//Filter Button
Padding(
padding: EdgeInsets.only(top: 10.0),
child: Align(
alignment: Alignment.topRight,
child: TextButton(
onPressed: () {
// Validate and handle the button press
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return buildFilterPopupContent();
},
);
},
style: TextButton.styleFrom(
foregroundColor: Colors.black,
backgroundColor: Color(0xFFF6F9FE), // Set the background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0), // Set the border radius
side: BorderSide(
color: Color(0xFFCDCDCF), // Set the border color
width: 2.0, // Set the border width
),
),
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 6.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.filter_alt_rounded,
color: Colors.black, // Set the color of the icon
),
SizedBox(width: 2.0), // Add some spacing between the icon and text
Text(
'Filter',
style: GoogleFonts.poppins(
fontSize: 16.0,
fontWeight: FontWeight.w400,
),
),
],
),
),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 20.0, bottom: 20.0),
child: buildListViewContent(),
),
],
),
),
),
),
buildSummaryViewContent(),
],
),
),
],
),
),
),
);
}
buildListViewContent() {
return Column(
children: jsonData.map((data) {
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0),
side: BorderSide(
color: Color(0xFFCDCDCF),
width: 2.0,
),
),
child: Theme(
data: Theme.of(context).copyWith(
unselectedWidgetColor: Colors.black,
colorScheme: ColorScheme.dark(
primary: Colors.black,
),
),
child: ExpansionTile(
title: Text(
data['name'],
style: GoogleFonts.poppins(
fontSize: 20.0,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
children: [
Padding(
padding: const EdgeInsets.only(left: 15.0, right: 15.0, bottom: 10.0),
child: const Divider(
color: Color(0xFFCDCDCF),
thickness: 1.5,
height: 10.0,
),
),
for (var key in ['totalSST', 'sstInService', 'sstOutOfService', 'cashLevelNormal', 'lowCashOutOfCash'])
Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
child: buildTotalNumberContainer(
title: key,
value: data[key],
),
),
],
),
Padding(
padding: const EdgeInsets.only(left: 20.0, right: 20.0, bottom: 10.0),
child: SizedBox(
width: double.infinity,
child: TextButton(
onPressed: () {},
style: TextButton.styleFrom(
backgroundColor: Color(0xFF3CADD9),
),
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Text(
'Detailed View',
style: GoogleFonts.inter(
fontSize: 16.0,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
),
),
],
),
),
),
);
}).toList(),
);
}
Widget buildTotalNumberContainer({required String title, required int value}) {
Color containerColor;
switch (title) {
case 'sstInService':
containerColor = Color(0xFF6EA4F4);
break;
case 'sstOutOfService':
containerColor = Color(0xFFF46E6E);
break;
case 'cashLevelNormal':
containerColor = Color(0xFF6EA4F4);
break;
case 'lowCashOutOfCash':
containerColor = Color(0xFFF46E6E);
break;
default:
containerColor = Color(0xFFB0B0B0); // Default color
}
return Padding(
padding: const EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10.0),
child: SizedBox(
width: double.infinity,
child: Container(
decoration: BoxDecoration(
color: containerColor.withOpacity(0.6),
borderRadius: BorderRadius.circular(2.0),
border: Border.all(
color: Color(0xFFCDCDCF),
width: 1.0,
),
),
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
getTitleForTotalNumberContainer(title),
style: GoogleFonts.poppins(
fontSize: 14.0,
fontWeight: FontWeight.w500,
),
),
Container(
width: 80.0,
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Color(0xFFF3F3F3).withOpacity(0.75),
borderRadius: BorderRadius.circular(10.0),
border: Border.all(
color: Color(0xFFCDCDCF),
width: 1.0,
),
),
child: Center(
child: Text(
value.toString(),
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
),
),
],
),
),
),
),
);
}
String getTitleForTotalNumberContainer(String title) {
switch (title) {
case 'totalSST':
return 'Total SST';
case 'sstInService':
return 'SST In-Service';
case 'sstOutOfService':
return 'SST Out-of-Service';
case 'cashLevelNormal':
return 'Cash Level Normal';
case 'lowCashOutOfCash':
return 'Low Cash / Out of Cash';
default:
return title;
}
}
buildSummaryViewContent() {
return Padding(
padding: EdgeInsets.only(top: 20.0, left: 15.0, right: 15.0, bottom: 10.0),
child: Container(
color: Colors.purple,
child: Text('Summary View'),
),
);
}
Future<void> _handleRefresh() async {
await Future.delayed(Duration(seconds: 2)); // Simulating a delay
setState(() {
loadJsonData(); // Replace this with your actual refresh logic
});
}
Widget buildFilterPopupContent() {
initializeSelectedItems();
return Container(
padding: EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Filter By Region',
style: GoogleFonts.inter(
fontSize: 16.0,
fontWeight: FontWeight.w700,
),
),
TextButton(
onPressed: () {
_resetCheckboxes();
},
child: Text(
'Reset',
style: GoogleFonts.inter(
fontSize: 16.0,
fontWeight: FontWeight.w600,
color: Color(0xFF3CADD9),
),
),
),
],
),
),
// Add a separator between the checkboxes and buttons
const Divider(height: 10, thickness: 1.0, color: Color(0xFFCDCDCF)),
// Wrap the jsonData names in a scrollable ListView with checkboxes
Expanded(
child: Padding(
padding: const EdgeInsets.all(0.0),
child: ListView.builder(
itemCount: jsonData.length,
itemBuilder: (context, index) {
return ListTile(
leading: Checkbox(
value: selectedItems[index],
onChanged: (value) {
setState(() {
selectedItems[index] = value!;
});
},
),
title: Text(
jsonData[index]['name'],
style: GoogleFonts.inter(
fontSize: 16.0,
fontWeight: FontWeight.w500,
),
),
onTap: () {
// Handle tap on the name if needed
},
);
},
),
),
),
// Add a separator between the checkboxes and buttons
const Divider(height: 10, thickness: 1.0, color: Color(0xFFCDCDCF)),
// Button Section
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
// Handle cancel button press
Navigator.pop(context); // Close the popup
},
child: Text(
'Cancel',
style: GoogleFonts.inter(
fontSize: 16.0,
fontWeight: FontWeight.w600,
),
),
),
),
SizedBox(width: 20.0),
Expanded(
child: ElevatedButton(
onPressed: () {
// Handle filter button press
// You can use the selectedItems list to get the selected items
// For example, you can print the selected names:
for (int i = 0; i < selectedItems.length; i++) {
if (selectedItems[i]) {
print('Selected: ${jsonData[i]['name']}');
}
}
initializeSelectedItems(); // Initialize the selectedItems list
Navigator.pop(context); // Close the popup
setState(() {}); // Trigger a rebuild to update the UI
},
child: Text(
'Apply',
style: GoogleFonts.inter(
fontSize: 16.0,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
],
),
);
}
void _resetCheckboxes() {
setState(() {
selectedItems = List<bool>.generate(jsonData.length, (index) => false);
});
}
}
The modal widget that is being called inside the builder of this showModalBottomSheet
:
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return buildFilterPopupContent();
},
);
is not reactive to the state of DashboardPage
. To make it works as expected, I suggest to move the state and all of its logic to a new stateful widget:
class FilterPopupContent extends StatefulWidget {
const FilterPopupContent({super.key});
@override
State<FilterPopupContent> createState() => _FilterPopupContentState();
}
class _FilterPopupContentState extends State<FilterPopupContent> {
// Put the states that was previously inside _DashboardPageState here (including all its logic)
List<bool> selectedItems = [];
// and other states that is needed by this popup content
@override
Widget build(BuildContext context) {
// Put the code that was previously inside buildFilterPopupContent method here
}
}
and then show the bottom sheet like this:
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return FilterPopupContent();
},
);
Also some other thing worth to read:
What is the difference between functions and classes to create reusable widgets?