skip to Main Content

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


  1. 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

    List<bool> iconColorList = List<bool>.filled(ProductData.size, false);
    

    and in the listview builder

                          IconButton(
                            icon: Icon(Icons.check, color: iconColorList[index] ? Colors.green : Colors.false),
                            onPressed: () {
                              setState(() {
                                if (iconColorList[index]) {
                                  iconColorList[index] = false;
                                } else {
                                  iconColorList[index] = true;
                                }
                              });
                            },
                          )
    
    Login or Signup to reply.
  2. 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 a List<int> to hold selected index
    , List<YourModelClass> or on your model create another bool variable bool icChecked = false.

    This approach is holding selected index on state class.

    On state class(_HomeScreenProductListState) create

    List<int> selectedIndex = []
    

    And on item tap event

    icon: Icon(Icons.check, color: selectedIndex.contains(index)?mySelectedColor:unSelectedColor)
    onPressed: () {
      if (selectedIndex.contains(index)) {
        selectedIndex.remove(index);
      } else {
        selectedIndex.add(index);
      }
    
      setState(() {});
    },
    

    mySelectedColor and unSelectedColor are two Color objects based on your need.

    Login or Signup to reply.
  3. 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:

      IconButton(
        icon: Icon(Icons.check, color: iconColor),
        onPressed: () {
          setState(() {
            if (iconColor != Colors.green) {
              iconColor = Colors.green;
            } else {
              iconColor = Colors.red;
            }
          });
        },
      )
    

    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.

    Login or Signup to reply.
  4. All List elements are referencing the same Color 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:

    class HomeScreenProductList extends StatefulWidget {
      const HomeScreenProductList({Key? key}) : super(key: key);
    
      @override
      State<HomeScreenProductList> createState() => _HomeScreenProductListState();
    }
    
    class _HomeScreenProductListState extends State<HomeScreenProductList> {
      // Sample data structure
      final productData = [
        {'id': '1', 'product-name': 'Product 1'},
        {'id': '2', 'product-name': 'Product 2'},
        {'id': '3', 'product-name': 'Product 3'},
        {'id': '4', 'product-name': 'Product 4'},
        {'id': '5', 'product-name': 'Product 5'},
      ];
    
      void _deleteProduct(String id) {
        productData.removeWhere((el) => el['id'] == id);
        setState(() {});
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: ListView.builder(
              itemCount: productData.length,
              itemBuilder: (context, index) {
                final product = productData[index];
    
                // Separate widget for each List element
                return ProductListViewTile(
                  key: Key('$index'),
                  product: product,
                  deleteProduct: _deleteProduct,
                );
              }),
        );
      }
    }
    
    
    // Extract List element to a separate widget and track it's state locally
    class ProductListViewTile extends StatefulWidget {
      const ProductListViewTile({
        Key? key,
        required this.product,
        required this.deleteProduct,
      }) : super(key: key);
    
      // Product to display
      final Map<String, String> product;
      // Callback method
      final void Function(String id) deleteProduct;
    
      @override
      State<ProductListViewTile> createState() => _ProductListViewTileState();
    }
    
    class _ProductListViewTileState extends State<ProductListViewTile> {
      Color iconColor = Colors.red;
    
      @override
      Widget build(BuildContext context) {
        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: const Color.fromARGB(100, 158, 158, 158),
            shape: const UnderlineInputBorder(borderSide: BorderSide(width: 1, color: Color.fromARGB(120, 220, 220, 220))),
            title: Text(widget.product['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");
                    widget.deleteProduct(widget.product['id']!);
                  },
                )
              ],
            ),
          ),
        );
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search