I have setup a Firestore database in which I have a collection ‘products’. I use a ListView builder to print them out on ListTiles.
I have also created leading "checkmark" IconButtons that appear for all ListTiles.
My goal is to be able to press whichever of these checkmark buttons and change their color independently. Currently, all of the checkmark buttons change color when you press one of them.
I don’t know how to achieve this and would appreciate some help.
class HomeScreenProductList extends StatefulWidget {
const HomeScreenProductList({Key? key}) : super(key: key);
@override
State<HomeScreenProductList> createState() => _HomeScreenProductListState();
}
class _HomeScreenProductListState extends State<HomeScreenProductList> {
final CollectionReference _productsCollection =
FirebaseFirestore.instance.collection('products');
final Stream<QuerySnapshot> _products = FirebaseFirestore.instance
.collection('products')
.orderBy('product-name')
.snapshots();
deleteProduct(id) async {
await _productsCollection.doc(id).delete();
}
Color tileColor = const Color.fromARGB(100, 158, 158, 158);
Color iconColor = Colors.red;
@override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _products,
builder: (
BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot,
) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text('Loading data');
}
final productData = snapshot.requireData;
return ListView.builder(
padding: const EdgeInsets.only(top: 16.0),
itemCount: productData.size,
itemBuilder: (context, index) {
return Card(
child: ListTile(
leading: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.check, color: iconColor),
onPressed: () {
setState(() {
if (iconColor != Colors.green) {
iconColor = Colors.green;
} else {
iconColor = Colors.red;
}
});
},
)
],
),
tileColor: tileColor,
shape: const UnderlineInputBorder(
borderSide: BorderSide(
width: 1, color: Color.fromARGB(120, 220, 220, 220))),
title: Text('${productData.docs[index]['product-name']}'),
textColor: const Color.fromARGB(255, 0, 0, 0),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.delete),
color: Colors.red,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
onPressed: () {
print("Delete Button Pressed");
deleteProduct(snapshot.data?.docs[index].id);
},
)
],
),
),
);
},
);
});
}
}
Full Code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const ShoppingListApp());
}
class ShoppingListApp extends StatelessWidget {
const ShoppingListApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return HomeScreenAppBar();
}
}
class HomeScreenAppBar extends StatelessWidget {
HomeScreenAppBar({Key? key}) : super(key: key);
final CollectionReference _products =
FirebaseFirestore.instance.collection('products');
final _controller = TextEditingController();
String? _productName;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color.fromARGB(245, 244, 253, 255),
appBar: AppBar(
backgroundColor: Colors.amberAccent,
leading: IconButton(
icon: const Icon(Icons.settings),
iconSize: 20,
color: Colors.black,
splashColor: Colors.white,
onPressed: () {
print("Settings Button Pressed");
},
),
title: TextFormField(
style: const TextStyle(fontSize: 18, fontFamily: 'Raleway'),
controller: _controller,
decoration: const InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide:
BorderSide(color: Color.fromARGB(245, 244, 253, 255))),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.black)),
hintText: 'Enter product',
hintStyle:
TextStyle(color: Color.fromARGB(200, 255, 255, 255))),
onChanged: (value) {
_productName = value;
},
),
actions: [
IconButton(
icon: const Icon(Icons.add),
iconSize: 24,
color: Colors.black,
splashColor: Colors.white,
onPressed: () {
if (_controller.text == '') {
return;
} else {
_products
.add({'product-name': _productName, 'isbought': false})
.then(
(value) => print('New Product "$_productName" Added'))
.catchError((error) => print(
'Failed To Add Product "$_productName": $error'));
_controller.clear();
}
},
)
],
),
body: const HomeScreenProductList());
}
}
class HomeScreenProductList extends StatefulWidget {
const HomeScreenProductList({Key? key}) : super(key: key);
@override
State<HomeScreenProductList> createState() => _HomeScreenProductListState();
}
class _HomeScreenProductListState extends State<HomeScreenProductList> {
final CollectionReference _productsCollection =
FirebaseFirestore.instance.collection('products');
final Stream<QuerySnapshot> _products = FirebaseFirestore.instance
.collection('products')
.orderBy('product-name')
.snapshots();
deleteProduct(id) async {
await _productsCollection.doc(id).delete();
}
Color tileColor = const Color.fromARGB(100, 158, 158, 158);
Color iconColor = Colors.red;
@override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _products,
builder: (
BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot,
) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text('Loading data');
}
final productData = snapshot.requireData;
return ListView.builder(
padding: const EdgeInsets.only(top: 16.0),
itemCount: productData.size,
itemBuilder: (context, index) {
return Card(
child: ListTile(
leading: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.check, color: iconColor),
onPressed: () {
setState(() {
if (iconColor != Colors.green) {
iconColor = Colors.green;
} else {
iconColor = Colors.red;
}
});
},
)
],
),
tileColor: tileColor,
shape: const UnderlineInputBorder(
borderSide: BorderSide(
width: 1, color: Color.fromARGB(120, 220, 220, 220))),
title: Text('${productData.docs[index]['product-name']}'),
textColor: const Color.fromARGB(255, 0, 0, 0),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.delete),
color: Colors.red,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
onPressed: () {
print("Delete Button Pressed");
deleteProduct(snapshot.data?.docs[index].id);
},
)
],
),
),
);
},
);
});
}
}
4
Answers
This is because you are using IconColor as a singular global variable. What you could possible do instead is create a boolean list for every data in the
ProductData
and in the listview builder
You are using single variable
iconColor
to change all items. That’s why all items are getting effected by changing any of it. You can create aList<int>
to hold selected index,
List<YourModelClass>
or on your model create another bool variablebool icChecked = false
.This approach is holding selected index on state class.
On state class(
_HomeScreenProductListState
) createAnd on item tap event
mySelectedColor
andunSelectedColor
are twoColor
objects based on your need.The reason all checkmarks change color is that you are saving the color in
iconColor
and then assigning that color to all the IconButtons.This happens in the following lines of code:
To achieve the desired behaviour you should save the state of every single checkmark.
Take a look at state management if you haven’t already.
All
List
elements are referencing the sameColor
variable. So, when that variable changes, all associated elements change accordingly.I suggest you create separate widget for your
itemBuilder
. That way you can track each element state internally. Here’s the example: