У меня есть страница корзины, на которой мне нужно увеличить количество товаров и отобразить общую сумму. Проблема в том, что мне нужно дважды нажать кнопку для увеличения количества, прежде чем она начнет обновлять общее количество товаров, но затем общая сумма. отображает значение, если бы я обычно увеличил количество один раз.
так изначально выглядит корзина, итог показывает правильный результат
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);
}
@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:
@AbdullahiAbbas, можем ли мы увидеть вашу службу базы данных? Просто интересно, нужно ли вам использовать StreamProvider.
@tbold я обновил вопрос, включив в него DatabaseService
Потому что вы пытаетесь прослушать значение от поставщика внутри обработчика событий, который здесь 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, количество обновляется, но затем проблема с отображением правильного значения общей суммы все еще сохраняется
Я думаю, что вы очень близки, но CartProvider
может быть излишним. Вот как я бы это структурировал, взглянув на пример из документации Firestore: https://firebase.flutter.dev/docs/firestore/usage/#realtime-changes.
В примере создается StreamProvider
, который прослушивает изменения в реальном времени из Firestore. Поэтому я в основном изменил файлы CartList
и Database
, чтобы следовать этому примеру.
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"),
),
),
],
),
],
),
),
);
}
}
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();
}
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
, он должен получать последние обновления.
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();
}
totalPrice
также должен обновиться. Поэтому я добавил звонок внутри _cartFromSnapshot
.// update the total before returning the mapping
updateTotalPrice();
return docs.map((doc) {
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
в корень приложения.
ты лучший, спасибо, это сработало!!!, я застрял здесь на неделю и никогда бы не подумал об этом, я очень ценю, что ты нашел время, чтобы помочь мне, ура, приятель!!!!
Можете ли вы попробовать использовать .watch вместо .read и посмотреть, сработает ли это? .read получает текущее значение провайдера, не прислушиваясь к изменениям, тогда как .watch запускает перестройку виджета при наличии изменений.