Кнопку Flutter необходимо нажать дважды, чтобы значение изменилось

У меня есть страница корзины, на которой мне нужно увеличить количество товаров и отобразить общую сумму. Проблема в том, что мне нужно дважды нажать кнопку для увеличения количества, прежде чем она начнет обновлять общее количество товаров, но затем общая сумма. отображает значение, если бы я обычно увеличил количество один раз.

так изначально выглядит корзина, итог показывает правильный результат

после однократного увеличения количества одного из товаров общая сумма не обновляется и остается как есть

после увеличения общей суммы во второй раз общая сумма обновляется, но значение неверное, потому что оно показывает сумму того, что должно было отображаться, когда я увеличил количество один раз

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"),
                  ),
                )
              ],
            )
          ],
        ),
      ),
    );
  }
}

ЭТО РОДИТЕЛЬСКИЙ ВИДЖЕТ ВЫШЕВИДЖЕТА

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(),
        ),
      ),
    );
  }
}

База данных.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);
  }

Можете ли вы попробовать использовать .watch вместо .read и посмотреть, сработает ли это? .read получает текущее значение провайдера, не прислушиваясь к изменениям, тогда как .watch запускает перестройку виджета при наличии изменений.

Harsh Mohan Sason 17.08.2024 02:18

@HarshMohanSason, я получаю эту ошибку Tried to listen to a value exposed with provider, from outside of the widget tree. This is likely caused by an event handler (like a button's onPressed) that called Provider.of without passing `listen: false`. To fix, write: Provider.of<DatabaseService>(context, listen: false); It is unsupported because may pointlessly rebuild the widget associated to the event handler, when the widget tree doesn't care about the value. The context used was: SliverList(delegate: SliverChildBuilderDelegate#6ed10(estimated child count: 3), renderObject:

Abdullahi Abbas 17.08.2024 13:58

@AbdullahiAbbas, можем ли мы увидеть вашу службу базы данных? Просто интересно, нужно ли вам использовать StreamProvider.

tbold 18.08.2024 22:14

@tbold я обновил вопрос, включив в него DatabaseService

Abdullahi Abbas 19.08.2024 00:08
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
96
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Потому что вы пытаетесь прослушать значение от поставщика внутри обработчика событий, который здесь onPressed(). Обработчики событий используются для запуска действий, а не для обновления пользовательского интерфейса. Просто создайте экземпляр поставщика вне обработчика в основном виджете, а затем просто используйте параметр корзины вашего потребителя, чтобы вызвать изменения пользовательского интерфейса в обработчике.

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

Затем в вашем обработчике onPressed()

 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()
}
},

Я попробовал ваше предложение, когда я устанавливаю listen: false, ничего не происходит, когда я пытаюсь увеличить/уменьшить количество, и нет сообщения об ошибке, но когда я устанавливаю значение true, количество обновляется, но затем проблема с отображением правильного значения общей суммы все еще сохраняется

Abdullahi Abbas 17.08.2024 16:57
Ответ принят как подходящий

Я думаю, что вы очень близки, но CartProvider может быть излишним. Вот как я бы это структурировал, взглянув на пример из документации Firestore: https://firebase.flutter.dev/docs/firestore/usage/#realtime-changes.

В примере создается StreamProvider, который прослушивает изменения в реальном времени из Firestore. Поэтому я в основном изменил файлы CartList и Database, чтобы следовать этому примеру.

  1. CartList должен отображать только данные. В настоящее время он пытается подсчитать общую сумму, но она должна быть получена из Stream/Firebase. Я внес изменения в функции onPressed и завернул все это в StreamProvider

(Я полностью удалил класс GetPrice, он некорректно обновлял значения из Firestore.)

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. Теперь необходимо изменить файл Database.dart, чтобы обеспечить фактическое обновление данных. У вас уже есть функция updateQuantity(), и она должна пройти через корзины и либо увеличить, либо уменьшить ее атрибуты quantity. Я думаю, что ваш должен измениться и выглядеть так:
  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. Общая стоимость также берется из DatabaseService, поэтому я просто добавил такую ​​переменную totalPrice:

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 = ...;
  }
}

Теперь, когда виджет CartList отображается totalPrice, он должен получать последние обновления.

  1. updateQuantity также следует обновить 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. При загрузке приложения totalPrice также должен обновиться. Поэтому я добавил звонок внутри _cartFromSnapshot.
// update the total before returning the mapping
updateTotalPrice();
    return docs.map((doc) {

  1. Наконец, ChangeNotifierProvider следует по возможности переместить в корень приложения. Очень простой пример файла main.dart будет выглядеть так:
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(),
        ));
  }
}

Надеюсь, это поможет! Вы можете оставить CartProvider, если хотите, просто попробуйте поставить ChangeNotifierProvider в корень приложения.

ты лучший, спасибо, это сработало!!!, я застрял здесь на неделю и никогда бы не подумал об этом, я очень ценю, что ты нашел время, чтобы помочь мне, ура, приятель!!!!

Abdullahi Abbas 22.08.2024 00:35

Другие вопросы по теме