skip to Main Content

i have a cart page where i need to be able to increase quantity of the items and display the total, the problem is i have to click on the button for increasing quantity twice before it starts to update the total of the items but then the total is displaying the value of if i would’ve normally increased the quantity once.

this is how the cart looks initially, the total is displaying the right result

after increasing the quantity of one of the items once, the total doesnt update and stays as is

after increasing the total the second time the total updates but the value is wrong because it is showing the total of what it was supposed to display when i increased the quantity once

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:watch_hub/models/cart.dart';
import 'package:watch_hub/services/database.dart';

class GetPrice {
  static num? price = 0;
}

class CartList extends StatefulWidget {
  const CartList({super.key});

  @override
  State<CartList> createState() => _CartListState();
}

class _CartListState extends State<CartList> {
  static int? price;
  @override
  Widget build(BuildContext context) {
    final carts = Provider.of<DatabaseService>(context);

    double maxWidth = MediaQuery.sizeOf(context).width;

    List totalPriceList = [];
    int totalQuantity;
    return Scaffold(
      appBar: AppBar(
        title: Text("Cart"),
      ),
      body: Consumer<List<Cart>>(builder: (context, cart, child) {
        return ListView.builder(
          itemCount: cart.length,
          itemBuilder: (
            context,
            index,
          ) {
            int counter = cart[index].price! * cart[index].quantity!;
            totalPriceList.add(counter);
            totalPriceList.reduce((value, element) {
              GetPrice.price = value + element;
              print("reduce value $value");
              print("reduce element $element");
            });
            print("price list is $totalPriceList");

            print("quantity list is ${GetPrice.quantity}");
            print("total is ${GetPrice.price.toString()}");

            return Column(
              children: [
                Container(
                  // height: 50,
                  child: ListTile(
                      leading: CircleAvatar(
                        backgroundImage: NetworkImage(
                          cart[index].image!,
                        ),
                      ),
                      title: Text(
                        cart[index].model!,
                        style: TextStyle(fontSize: 20),
                      ),
                      subtitle: Text(
                        cart[index].brand!,
                        style: TextStyle(fontSize: 20),
                      ),
                      trailing: Row(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          IconButton.filled(
                            style: IconButton.styleFrom(
                                backgroundColor: Colors.red),
                            onPressed: () {
                              if (cart[index].quantity != 1) {
                                var cont = context.read<DatabaseService>();
                                cart[index].quantity =
                                    cart[index].quantity! - 1;
                                /*ALL THE BELOW METHOD DOES IS CALL THE notifyListeners() METHOD*/
                                   cont.updateQuantity();
                              }
                            },
                            icon: Icon(Icons.remove),
                          ),
                          Column(
                            children: [
                              Text(
                                cart[index].price.toString(),
                                style: TextStyle(fontSize: 20),
                              ),
                              Text(
                                "QTY: ${GetPrice.quantity.toString()}",
                                style: TextStyle(fontSize: 16),
                              )
                            ],
                          ),
                          IconButton.filled(
                            style: IconButton.styleFrom(
                                backgroundColor: Colors.green),
                            onPressed: () {
                              var cont = context.read<DatabaseService>();
                              cart[index].quantity = cart[index].quantity! + 1;
                              print("increase price is ${GetPrice.price}");
                            //ALL THE BELOW METHOD DOES IS CALL THE notifyListeners() METHOD
                              cont.updateQuantity();

                            },
                            icon: Icon(Icons.add),
                          ),
                        ],
                      )),
                ),
              ],
            );
          },
        );
      }),
      bottomSheet: Container(
        height: 130,
        color: Colors.brown[400],
        child: Column(
          children: <Widget>[
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                const Padding(
                  padding: EdgeInsets.all(8.0),
                  child: Text(
                    "Total ",
                    style: TextStyle(fontSize: 25, color: Colors.white),
                  ),
                ),
                Padding(
                  padding: EdgeInsets.all(8.0),
                  child: Text(
                    "$${GetPrice.price.toString()}",
                    style: const TextStyle(fontSize: 25, color: Colors.white),
                  ),
                ),
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                SizedBox(
                  width: 200,
                  child: FloatingActionButton(
                    onPressed: () {},
                    child: Text("Make Order"),
                  ),
                )
              ],
            )
          ],
        ),
      ),
    );
  }
}

THIS IS THE PARENT WIDGET OF THE ABOVE WIDGET

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:watch_hub/models/cart.dart';
import 'package:watch_hub/models/watch.dart';
import 'package:watch_hub/screens/home/cart_list.dart';
import 'package:watch_hub/screens/home/watch_list.dart';
import 'package:watch_hub/services/database.dart';

class CartProvider extends StatefulWidget {
  const CartProvider({super.key});

  @override
  State<CartProvider> createState() => _CartProviderState();
}

class _CartProviderState extends State<CartProvider> {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => DatabaseService(),
      child: StreamProvider<List<Cart>>.value(
        value: DatabaseService().carts,
        initialData: const [],
        child: Scaffold(
          body: CartList(),
        ),
      ),
    );
  }
}

Database.dart

  final DocumentReference<Map<String, dynamic>> cart =
      FirebaseFirestore.instance.collection("Cart").doc(user!.uid);
    

  List<Cart> _cartFromSnapshot(
      DocumentSnapshot<Map<String, dynamic>> snapshot) {
    List docs = [];
    List docs1 = [];
    docs.add(snapshot.data()!);
    docs1 = docs[0]['cart'];
    print("docs1 says $docs1");

    return docs1.map((doc) {
      print("cart brand is ${doc['brand']}");
      return Cart(
        brand: doc['brand'] ?? '',
        model: doc['model'] ?? '',
        price: doc['price'] ?? 0,
        image: doc['image'] ?? '',
        quantity: doc['quantity'] ?? 0,
      );
    }).toList();
  }

  Stream<List<Cart>> get carts {
    return cart.snapshots().map(_cartFromSnapshot);
  }

2

Answers


  1. Because you are trying to listen to a value from the provider inside an event handler which is onPressed() here. Event handlers are used to trigger actions rather than updating the UI. Just simply create an instance of provider outside the handler in the main widget and then just use your cart parameter of your consumer to trigger UI changes in the handler

    Widget build(BuildContext context) {
    final carts = Provider.of<DatabaseService>(context, listen: false); //Use listen: false initially to not trigger UI update
    
    double maxWidth = MediaQuery.sizeOf(context).width;
    
    List totalPriceList = [];
    int totalQuantity;
    ....
    

    Then in your onPressed() handler

     onPressed: () {
    if (cart[index].quantity != 1) {
      // remove this line-> var cont = context.read < DatabaseService > ();
     
      cart[index].quantity =
        cart[index].quantity!-1;
      /*ALL THE BELOW METHOD DOES IS CALL THE notifyListeners() METHOD*/
       carts.updateQuantity();  //use the carts.updateQuantity()
    }
    },
    
    Login or Signup to reply.
  2. I think you are very close but CartProvider might be redundant. Here is how I would structure it by looking at the example from Firestore’s docs here: https://firebase.flutter.dev/docs/firestore/usage/#realtime-changes.

    The example creates a StreamProvider which listens to real-time changes from Firestore. So I mainly changed CartList and Database files to follow this example.

    1. CartList should only display data. Currently, it is trying to calculate the total but the total should come from the Stream/Firebase. I made changes to the onPressed functions and wrapped the whole thing in StreamProvider

    (I removed GetPrice class entirely, it was not staying up to date with values from Firestore correctly.)

    import 'package:cloud_firestore/cloud_firestore.dart';
    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    
    // I named my Database.dart as DatabaseService.dart, fyi
    import 'DatabaseService.dart';
    
    class Cart {
      String? brand;
      int? quantity;
      int? price;
      String? image;
      String? model;
      Cart({this.brand, this.quantity, this.price, this.image, this.model});
    }
    
    class CartList extends StatefulWidget {
      const CartList({super.key});
    
      @override
      State<CartList> createState() => _CartListState();
    }
    
    class _CartListState extends State<CartList> {
      @override
      Widget build(BuildContext context) {
        // get the stream from the provider
        final carts = Provider.of<DatabaseService>(context).carts;
        return Scaffold(
          appBar: AppBar(
            title: Text("Cart"),
          ),
          // this body now accepts data from a Stream
          body: StreamBuilder<List<Cart>>(
            stream: carts,
            builder: (context, snapshot) { 
              if (!snapshot.hasData) {
                return SizedBox.shrink();
              }
              // snapshot.data is where the list of Cart should come in
              var data = snapshot.data!;
    
              // this ListView.builder stayed more or less the same
              return ListView.builder(
                itemCount: data.length,
                itemBuilder: (context, index) {
                      return ListTile(
                         leading: CircleAvatar(
                           backgroundImage: NetworkImage(
                             data[index].image!,
                           ),
                         ),
                        title: Text(
                          data[index].model!,
                          style: TextStyle(fontSize: 20),
                        ),
                        subtitle: Text(
                          data[index].brand!,
                          style: TextStyle(fontSize: 20),
                        ),
                        trailing: Row(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            IconButton.filled(
                              style: IconButton.styleFrom(
                                backgroundColor: Colors.red,
                              ),
                              onPressed: () {
                                // major changes here, the widget will notify Firestore to change its value. It doesn't have to do anything else.
                                Provider.of<DatabaseService>(context, listen: false)
                                    .updateQuantity(index, -1);
                              },
                              icon: Icon(Icons.remove),
                            ),
                            Column(
                              children: [
                                Text(
                                  data[index].price.toString(),
                                  style: TextStyle(fontSize: 20),
                                ),
                                Text(
                                  "QTY: ${data[index].quantity.toString()}",
                                  style: TextStyle(fontSize: 16),
                                ),
                              ],
                            ),
                            IconButton.filled(
                              style: IconButton.styleFrom(
                                backgroundColor: Colors.green,
                              ),
                              onPressed: () {
                               // major changes here too, the widget will notify Firestore to change its value.
                                Provider.of<DatabaseService>(context, listen: false)
                                    .updateQuantity(index, 1);
                              },
                              icon: Icon(Icons.add),
                            ),
                          ],
                        )
                  );
                },
              );
            }),
          bottomSheet: Container(
            height: 130,
            color: Colors.brown[400],
            child: Column(
              children: <Widget>[
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    const Padding(
                      padding: EdgeInsets.all(8.0),
                      child: Text(
                        "Total ",
                        style: TextStyle(fontSize: 25, color: Colors.white),
                      ),
                    ),
                    Padding(
                      padding: EdgeInsets.all(8.0),
                      child: Text( 
                        // another major change here, the widget will get the totalPrice from the provider.
                        "$${Provider.of<DatabaseService>(context).totalPrice}",
                        style: const TextStyle(fontSize: 25, color: Colors.white),
                      ),
                    ),
                  ],
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    SizedBox(
                      width: 200,
                      child: FloatingActionButton(
                        onPressed: () {},
                        child: Text("Make Order"),
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        );
      }
    }
    
    
    1. Now the Database.dart file has to be changed to take care of actually updating data. You already have an updateQuantity() function and this should go through the carts and either increment or decrement its quantity attributes. I think yours should change to look like this:
      void updateQuantity(int index, int quantity) async {
        // this is just an example 
        // how you get your data/snapshot can change
        var doc = await cart.get().data!;
        data[index]['quantity'] += quantity;
        
        // tell firestore to update
        await cart.update(data);
        // tell listeners to rebuild
        notifyListeners();
      }
    
    1. The total price also comes from the DatabaseService so I just added a totalPrice variable like this:
    
    class DatabaseService extends ChangeNotifier {
      int totalPrice = 0;
    
      void updateTotalPrice() async {
        // another example of getting the data
        var doc = await cart.get().data!;
        // and loop through to calculate price
        for (var item in doc) {
           // do math here
        }
        // update the class variable with calculation from above loop
        totalPrice = ...;
      }
    }
    

    Now when the CartList widget is displaying totalPrice it should get the latest.

    1. updateQuantity should also update the totalPrice.
    void updateQuantity(int index, int quantity) async {
        // do what you need to do to update quantity
        
        // add the helper function to update totalPrice
        updateTotalPrice();
        // and don't forget to rebuild listeners
        notifyListeners();
      }
    
    1. On app load, the totalPrice should also be updated. So I added a call inside _cartFromSnapshot.
    // update the total before returning the mapping
    updateTotalPrice();
        return docs.map((doc) {
    
    
    1. Lastly, the ChangeNotifierProvider should move to the root of the app if possible. A very simple example of a main.dart file would look like this:
    import 'firebase_options.dart';
    
    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform,
      );
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        // moved ChangeNotifierProvider to root
        return ChangeNotifierProvider(
            create: (context) => DatabaseService(),
            child: MaterialApp(
              title: 'Flutter Demo',
              theme: ThemeData(
                colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
                useMaterial3: true,
              ),
              home: CartList(),
            ));
      }
    }
    

    Hope this helps! You can keep CartProvider if you want, just try to put the ChangeNotifierProvider at the root of the app.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search