How to show total item of the listview? - flutter

Please help me. I want to show total items of the list view in the card. Basically, first it will show all 3 category. If you click one of the category it will show all the list of item. So, the problem I want to solve is to show the total of item based on the category.
Coding below i tried using .length and List but it does not show the total of item I register.
class CaseListCategory extends StatefulWidget {
const CaseListCategory ({Key? key}) : super (key : key);
#override
_CaseListCategoryState createState() => _CaseListCategoryState();
}
class _CaseListCategoryState extends State<CaseListCategory> {
#override
Widget build(BuildContext context) {
final docCase = FirebaseFirestore.instance.collection('cases').doc();
List<CriticalCaseList> cases = [];
return Scaffold(
body: Container(
padding: EdgeInsets.all(10),
child: ListView(
children: <Widget>[
GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => CriticalCaseList(),
),
);
},
child: Card(
elevation: 10,
color: Colors.red,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 10),
child: Container(
child: Column(
children: [
Text(
'CRITICAL',
style: TextStyle(letterSpacing: 1.0,fontSize: 20, fontWeight: FontWeight.bold),
),
Text(
"${cases.length}",
style: TextStyle(letterSpacing: 1.0,fontSize: 20, fontWeight: FontWeight.bold),
),
],
),
),
),
),
),
GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ModerateCaseList(),
),
);
},
child: Card(
elevation: 10,
color: Colors.orange,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 10),
child: Column(
children: [
Text(
"MODERATE",
style: TextStyle(letterSpacing: 1.0,fontSize: 20, fontWeight: FontWeight.bold),
),
Text(
"${cases.length}",
style: TextStyle(letterSpacing: 1.0,fontSize: 20, fontWeight: FontWeight.bold),
),
],
),
),
),
),
GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LowCaseList(),
),
);
},
child: Card(
elevation: 10,
color: Colors.yellow,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 10),
child: Column(
children: [
Text(
'LOW',
style: TextStyle(letterSpacing: 1.0,fontSize: 20, fontWeight: FontWeight.bold),
),
Text(
"${cases.length}",
style: TextStyle(letterSpacing: 1.0,fontSize: 20, fontWeight: FontWeight.bold),
),
],
),
),
),
),
],),
)
);
}
}
Here are the coding of page after i click the card. it show all of item
Category page
List of item page
class CriticalCaseList extends StatefulWidget {
const CriticalCaseList ({Key? key}) : super (key : key);
#override
_CriticalCaseListState createState() => _CriticalCaseListState();
}
class _CriticalCaseListState extends State<CriticalCaseList> {
User? user = FirebaseAuth.instance.currentUser;
final CollectionReference _cases = FirebaseFirestore.instance.collection('cases');
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Critical Case"),
backgroundColor: Colors. redAccent,
centerTitle: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
color: Colors.white,
iconSize: 30,
onPressed: () => Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context) => const VolunteerPage())),
),
),
// Using StreamBuilder to display all products from Firestore in real-time
body: StreamBuilder(
stream: _cases.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
if (streamSnapshot.hasData) {
return ListView.builder(
itemCount: streamSnapshot.data!.docs.length,
itemBuilder: (context, index) {
final DocumentSnapshot documentSnapshot = streamSnapshot.data!.docs[index];
if(documentSnapshot['priority'] == "Critical" && documentSnapshot['status'] == "Waiting for rescue"){
return Card(
child: ListTile(
title: Text(documentSnapshot['name']),
subtitle: Text(documentSnapshot['priority'].toString()),
trailing: Icon(Icons.arrow_forward),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => CaseListView(cid: documentSnapshot['cid']))
);
},
)
);
}
return Card();
},
);
}
return const Center(
child: CircularProgressIndicator(),
);
},
),
);
}
}

Problems
There are two problems with the code.
In CriticalCaseList, documentSnapshot is a snapshot and not the document data itself, so you need to access data from it after calling .data() on the documentSnapshot.
In CaseListCategory, cases.length is not working because there is no part of the code that is filling up cases from Firestore.
It will be improper to use cases.length because cases will contain the total of all the cases (irrespective of their categories). But you want the individual totals of critical, moderate, or low categories.
Solution
So in CriticalCaseList, change the following line
final DocumentSnapshot documentSnapshot = streamSnapshot.data!.docs[index];
to
final DocumentSnapshot documentSnapshot = streamSnapshot.data!.docs[index].data();
(notice .data() appended at the end)
Given that you want to display the total number of cases in each category, what you can do is keep track of each categories total.
So you can have variables for each of them. Then initialize their values in initState. And still in initState, you can use .snapshots().listen() on the collection reference of cases. This way, every time cases are added or removed, The CaseListCategory widget will update the total of each case and display the current total. Also, remember to cancel the StreamSubscription in dispose().
Finally, in the parts of the code where you display the totals, instead of using cases.length, you use the total of the given category. The following should work:
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'critical_case_list.dart';
import 'moderate_case_list.dart';
import 'low_case_list.dart';
class CaseListCategory extends StatefulWidget {
const CaseListCategory({Key? key}) : super(key: key);
#override
_CaseListCategoryState createState() => _CaseListCategoryState();
}
class _CaseListCategoryState extends State<CaseListCategory> {
double _criticalCases = 0, _moderateCases = 0, _lowCases = 0;
late StreamSubscription _listener;
#override
void initState() {
super.initState();
_listener = FirebaseFirestore.instance
.collection('cases')
.snapshots()
.listen((snap) {
final cases = snap.docs.map((doc) => doc.data());
_criticalCases = 0;
_moderateCases = 0;
_lowCases = 0;
for (var caseData in cases) {
if (caseData['priority'] == 'Critical') _criticalCases++;
if (caseData['moderate'] == 'Moderate') _moderateCases++;
if (caseData['low'] == 'Low') _lowCases++;
}
setState(() {});
});
}
#override
void dispose() {
_listener.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(10),
child: ListView(
children: <Widget>[
GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => CriticalCaseList(),
),
);
},
child: Card(
elevation: 10,
color: Colors.red,
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 10),
child: Container(
child: Column(
children: [
Text(
'CRITICAL',
style: TextStyle(
letterSpacing: 1.0,
fontSize: 20,
fontWeight: FontWeight.bold),
),
Text(
'$_criticalCases',
style: TextStyle(
letterSpacing: 1.0,
fontSize: 20,
fontWeight: FontWeight.bold),
),
],
),
),
),
),
),
GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ModerateCaseList(),
),
);
},
child: Card(
elevation: 10,
color: Colors.orange,
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 10),
child: Column(
children: [
Text(
"MODERATE",
style: TextStyle(
letterSpacing: 1.0,
fontSize: 20,
fontWeight: FontWeight.bold),
),
Text(
'$_moderateCases',
style: TextStyle(
letterSpacing: 1.0,
fontSize: 20,
fontWeight: FontWeight.bold),
),
],
),
),
),
),
GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LowCaseList(),
),
);
},
child: Card(
elevation: 10,
color: Colors.yellow,
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 10),
child: Column(
children: [
Text(
'LOW',
style: TextStyle(
letterSpacing: 1.0,
fontSize: 20,
fontWeight: FontWeight.bold),
),
Text(
'$_lowCases',
style: TextStyle(
letterSpacing: 1.0,
fontSize: 20,
fontWeight: FontWeight.bold),
),
],
),
),
),
),
],
),
));
}
}
Better solution
Your current setup is expensive. Or rather, it will be costly as your app grows or scales. Firebase charges you for every document read. That said, every time CaseListCategory is loaded, Firestore will read all the documents in the cases collection. And every time any document in that collection is created, updated, or deleted, Firestore will fetch all of them again (to update the totals).
A common pattern to reduce such cost is to have a counters collection. Inside it, you will have documents for each category that will maybe have a total property, holding the current total of a given category.
Then you increment or decrement the current count of a given category when a case is created or deleted. A better place to run this logic is in Cloud Functions, where you are sure that the code would run. Updating the counts in clients is not recommended because the client's network might fail or also for security reasons.
So you could have the following code in cloud functions in index.js file.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.incrementCaseCount = functions.firestore
.document('/cases/{caseId}')
.onCreate(async (snap, _) => {
const category = snap.data()['priority'].toLowerCase();
await db
.doc(`/counters/${category}Cases`)
.set({ total: admin.firestore.FieldValue.increment(1) }, { merge: true })
.catch((error) => console.error(error));
});
exports.decrementCaseCount = functions.firestore
.document('/cases/{caseId}')
.onDelete(async (snap, _) => {
const category = snap.data()['priority'].toLowerCase();
await db
.doc(`/counters/${category}Cases`)
.set({ total: admin.firestore.FieldValue.increment(-1) }, { merge: true })
.catch((error) => console.error(error));
});
And then in flutter, in the initState of CaseListCategory, instead of listening to snapshots of the entire cases collection, you can listen to snapshots of only the counters collection. counters collection would have a small number of documents, so it is cheaper to read from them than to read all the documents in the cases collection.
So you can have the following in initState.
_listener = FirebaseFirestore.instance
.collection('counters')
.snapshots()
.listen((snap) {
for (var doc in snap.docs) {
if (doc.id == 'criticalCases') _criticalCases = doc.data()['total'];
if (doc.id == 'moderateCases') _moderateCases = doc.data()['total'];
if (doc.id == 'lowCases') _lowCases = doc.data()['total'];
}
setState(() {});
});

Related

Update item widget from local database list

I'm using a ready-made local database in my application and the problem is that I can't update one item from the list. If I add a chapter to favorites, then the button's state is updated only after the page is reopened. Likewise, the favorites list is updated only when the page is reopened. Right now when I add/remove favorites, I dynamically load the entire list so that it updates the values, but I only need to update one item, how can I do this using a provider? I didn’t give code examples, because I want to understand exactly the logic of actions
UPD:
My code:
#override
Widget build(BuildContext context) {
return FutureBuilder<List>(
future: _databaseQuery.getAllChapters(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.connectionState == ConnectionState.done &&
snapshot.hasData
? CupertinoScrollbar(
child: ListView.builder(
physics: const BouncingScrollPhysics(),
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
return MainChapterItem(
item: snapshot.data![index],
);
},
),
)
: const Center(
child: CircularProgressIndicator.adaptive(),
);
},
);
}
Item:
final MainChapterItemModel item;
#override
Widget build(BuildContext context) {
return Material(
child: InkWell(
child: Container(
padding: const EdgeInsets.all(8),
child: Row(
children: [
IconButton(
icon: item.favoriteState == 0
? const Icon(CupertinoIcons.bookmark)
: const Icon(CupertinoIcons.bookmark_fill),
splashRadius: 22,
splashColor: const Color(0xff81b9b0),
onPressed: () {
context.read<BookmarkButtonState>().addRemoveChapterBookmark(
item.favoriteState == 0 ? 1 : 0, item.id);
},
),
const SizedBox(
width: 8,
),
Flexible(
child: ListTile(
contentPadding: EdgeInsets.zero,
title: Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
item.chapterNumber,
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
),
subtitle: Html(
data: item.chapterTitle,
style: {
'#': Style(
fontSize: const FontSize(17),
padding: EdgeInsets.zero,
margin: EdgeInsets.zero,
),
'small': Style(
fontSize: const FontSize(8),
),
'a': Style(
fontSize: const FontSize(14),
color: Colors.blue,
),
},
),
),
),
],
),
),
onTap: () {},
),
);
}
The problem is that when I add to favorites or delete, the button state is not updated. And in the favorites list, the item is not deleted on click, but it disappears after the page is reopened:
IconButton(
icon: item.favoriteState == 0
? const Icon(CupertinoIcons.bookmark)
: const Icon(CupertinoIcons.bookmark_fill),
splashRadius: 22,
splashColor: const Color(0xff81b9b0),
onPressed: () {
context.read<BookmarkButtonState>().addRemoveChapterBookmark(
item.favoriteState == 0 ? 1 : 0, item.id);
},
),
Provider code:
final DatabaseQuery _databaseQuery = DatabaseQuery();
DatabaseQuery get getDatabaseQuery => _databaseQuery;
addRemoveChapterBookmark(int state, int chapterId) {
_databaseQuery.addRemoveFavoriteChapter(state, chapterId);
notifyListeners();
I solved the problem by signing all lists to listen to databaseQuery in the provider:
future: context.watch<BookmarkButtonState>().getDatabaseQuery.getAllChapters(),

flutter Problem: How to update number of items count in cart?

I implement to add to cart functionality items added into cart successfully but the number of count in the cart badge is not updated when I reload dart page than the number of count updates.can anyone help me?
I implement to add to cart functionality items added into cart successfully but the number of count in the cart badge is not updated when I reload dart page than the number of count updates.can anyone help me?
This is my Homepage.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:badges/badges.dart';
import 'package:hospital/BestDeatProducts/best_deal_product_page.dart';
import 'package:hospital/CartPage/pages/cartPage.dart';
import 'package:hospital/Drawer/dropdown_menu.dart';
import 'package:hospital/FirstSection/carousel.dart';
import 'package:hospital/Drawer/drawercontent.dart';
import 'package:hospital/FloatingActionButton/ConsultWithDoctor/consult_with_doctor.dart';
import 'package:hospital/MedicineCateory/medicine_category_page.dart';
import 'package:hospital/SecondSection/second_page.dart';
import 'package:hospital/ThirdSection/third_page.dart';
import 'package:hospital/TrendingProducts/trending_product_page.dart';
import 'package:hospital/constant.dart';
import 'package:hospital/customApiVariable.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'No Internet/connectivity_provider.dart';
import 'No Internet/no_internet.dart';
import 'package:http/http.dart' as http;
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
SharedPreferences loginData;
// late String username;
Future getUsername() async {
loginData = await SharedPreferences.getInstance();
setState(() {
// print("uname" + uname.toString());
print("dddpppuu1 : responceData_un" +
loginData.getString('responceData_un').toString());
print("dddpppuu2 : responceData_ue" +
loginData.getString('responceData_ue').toString());
print("dddpppuu3 : responceData_status" +
loginData.getString('responceData_status').toString());
String responceData_uid =
loginData.getString('responceData_uid').toString();
fetchData(responceData_uid);
});
}
var response;
var addToCartApi;
#override
void initState() {
// TODO: implement initState
//
super.initState();
Provider.of<ConnectivityProvider>(context, listen: false).startMonitering();
// for loading
getUsername();
}
fetchData(String argResponceData_uid) async {
var api = Uri.parse(
'$ecommerceBaseUrl/addToCartApi.php?a2rTokenKey=$a2rTokenKey&action=addToCartList&uid=${argResponceData_uid}');
print('cartpage' + api.toString());
response = await http.get(api);
print("Carousel" + response.body);
addToCartApi = jsonDecode(response.body);
print('addToCartApi' + addToCartApi['total'].toString());
print('totalPriceAfterOffer' + totalPriceAfterOffer.toString());
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: kGreen,
title: Text(
"BK Arogyam",
style: TextStyle(fontStyle: FontStyle.italic),
),
actions: [
response != null
? Badge(
position: BadgePosition.topEnd(top: 3, end: 18),
animationDuration: Duration(milliseconds: 300),
animationType: BadgeAnimationType.slide,
badgeContent: Text(
addToCartApi['total']['num'].toString(),
style: TextStyle(color: Colors.white),
),
child: IconButton(
icon: Icon(Icons.shopping_cart),
padding: EdgeInsets.only(right: 30.0),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Cartpage()),
);
}),
)
: IconButton(
icon: Icon(Icons.shopping_cart),
// onPressed: () => print("open cart"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Cartpage()),
);
},
),
DropDownMenu(),
],
),
floatingActionButton: FloatingActionButton(
backgroundColor: kGreen,
onPressed: () => Navigator.push(context,
MaterialPageRoute(builder: (context) => ConsultWithDoctor())),
tooltip: 'Consult With Doctor',
child: Container(
child: Image(
image: AssetImage(
"assets/icons/cwd.png",
),
color: Colors.white,
width: 40,
height: 40,
),
),
),
drawer: Drawer(
child: DrawerContent(),
),
body: pageUI());
}
Widget pageUI() {
return Consumer<ConnectivityProvider>(
builder: (consumerContext, model, child) {
if (model.isOnline != null) {
return model.isOnline
? ListView(
children: [
Carousel(),
SizedBox(
height: 10.0,
),
MedicineCategoryPage(),
SizedBox(
height: 10.0,
),
SecondPage(),
SizedBox(
height: 10.0,
),
ThirdPage(),
SizedBox(
height: 10.0,
),
TrendingProductPage(),
SizedBox(
height: 16.0,
),
BestDealProductPage(),
SizedBox(
height: 10.0,
),
],
)
: NoInternet();
}
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
},
);
}
}
You can use the change notifier provide by provider package.
And watch the video on how to use, exactly for ur use case search YouTube change notifier provider by the growing developer
Hope it helps 🙂
You can use provider(provider: ^5.0.0) or Getx(get: ^4.1.4) to handle this kind of case.
There are lots of examples are available for GetX and Provider.
If you don't want to use any of them, Then store your cart/badge count to tempCartCount variable(Example: int cartCount = 0) and set it to the badge count instead of "addToCartApi['total']['num'].toString()" , Make sure to setState on update/addCart Item.
Here I provide a simple example of how to update count on appBar.
if you want to change from any other screen make cartCount to global otherwise you can set it local/private.
import 'package:badges/badges.dart';
import 'package:flutter/material.dart';
class UpdateCountExample extends StatefulWidget {
#override
_UpdateCountExampleState createState() => _UpdateCountExampleState();
}
int cartCount = 0;
class _UpdateCountExampleState extends State<UpdateCountExample> {
List<String> cartArray = [];
#override
void initState() {
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
setState(() {
cartCount = 0;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("App Bar"),
actions: [
Padding(
padding: const EdgeInsets.only(right: 18.0, top: 5.0),
child: Badge(
badgeContent: Text(cartCount.toString()),
child: Icon(Icons.add_shopping_cart),
),
)
],
),
body: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Add item in cart",
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18, decoration: TextDecoration.none, color: Colors.black),
),
SizedBox(
height: 20,
),
InkWell(
onTap: () {
setState(() {
cartArray.add("value ${cartArray.length}");
cartCount = cartArray.length;
});
},
child: Container(
padding: const EdgeInsets.all(10.0),
color: Colors.amber,
child: Text(
"Add Item",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 15, decoration: TextDecoration.none, color: Colors.black),
),
),
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: cartArray.length,
itemBuilder: (context, index) {
return Text(
cartArray[index],
style: TextStyle(fontSize: 20, color: Colors.black),
);
}),
)
],
),
),
);
}
}
I have used StreamBuilder to update cart items instantly.
You can use the code from this post
How to use Streambuilder in flutter

How to manually add items to listview in Flutter

I have a list of cart items which I am displaying using the code below. Right after this list, I would like to display the total amount. This is how the final result should look like:
Chicken Burger 1X $20.5
Chicken Wrap 1X $9.99
Total $30.49
Container(
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 4),
height: min(widget.order.products.length * 20.0 + 10, 100),
child: ListView(
children: widget.order.products
.map(
(prod) => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(child:Text(
prod.title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
)),
Text(
'${prod.quantity}x \$. ${prod.price}',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
)
],
),
)
.toList(),
How can I append total to this list?
Here is my suggestion.
I used spread operator to ListView's children for adding Widget related to 'total'.
Additionally I added one item at Container's height because of Total item in ListView.
Below is summary code that I did.
ListView(
children: <Widget> [
...list.map(...).toList(),
TotalWidget(),
]
)
This is full code based your code.
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: _buildBody(),
floatingActionButton: FloatingActionButton(
onPressed: () {
showModalBottomSheet(
context: context,
backgroundColor: Colors.blueGrey,
isScrollControlled: false,
builder: (context) => Wrap(
children: [
ListView.separated(
shrinkWrap: true,
itemCount: 3,
itemBuilder: (BuildContext context, int index) => ListTile(
title: Text(
'lists[index].listName',
style: TextStyle(
color: Colors.white,
),
),
),
separatorBuilder: (BuildContext context, int index) =>
Divider(),
),
],
),
);
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
Widget _buildBody() {
List<Product> listProduct = [
Product('Chicken Burger', 1, 20.5),
Product('Chicken Wrap', 1, 9.99),
];
double totalAmount = 0;
for (var item in listProduct) {
totalAmount += (item.price * item.quantity);
}
return Container(
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 4),
height: min((listProduct.length + 1) * 20.0 + 10, 100),
child: ListView(
children: [
...listProduct
.map(
(prod) => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Text(
prod.title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
)),
Text(
'${prod.quantity}x \$. ${prod.price}',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
)
],
),
)
.toList(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Text(
'Total',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
)),
Text(
'$totalAmount',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
)
])
],
),
);
}
}
class Product {
String title;
int quantity;
double price;
Product(this.title, this.quantity, this.price);
}
Edit 1, after op updated more info in comments:
Column(children: [ Text(widget.order.totalPrice.toString()),
Flexible(child:
ListView(
children:
widget.order.products
.map((prod) => Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(child:Text(
prod.title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
)),
Text(
'${prod.quantity}x \$. ${prod.price}',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
)
],
),
)
.toList())]),
Since total isn't being stored as a single variable your list\cart object. You need to create a double totalPrice = 0.0;
then use a forLoop to add the values
for (var prod in widget.order.products) {
totalPrice += (prod.price * prod.quantity);}
Display this totalPrice wherever you want, you can't have it in the listView though.
If you want add items to ListView, first you have to add those items to your List (for example order.products or new one) and then use state management approach to re render ListView. if your logic is simple you can use stateful widget.
example code:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: MyList(),
),
);
}
}
class MyList extends StatefulWidget {
#override
_MyListState createState() => _MyListState();
}
class _MyListState extends State<MyList> {
List<String> orders = ["order1", "order2", "order3"];
#override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: ListView(
children: orders
.map(
(String e) => Card(
child: ListTile(
title: Text(e),
),
),
)
.toList(),
),
),
TextButton(
onPressed: () {
List<String> extraFields = ["field1", "field2"];
setState(
() {
orders.addAll(extraFields);
},
);
},
child: Text("Add extra fields"),
),
],
);
}
}
For that, you'd have to learn state management. To make things simple we'll use the built-in StreamBuilder to provide the data. Its best practice to separate your ui from your business logic so I'll do it here.
In order to use a StreamBuilder, you'd have to provide it a Stream<T> where T is your variable's type. In your case, its a List<String>. Lets write it in another file that holds all your buisness logic.
product_bloc.dart:
class ProductBloc {
final List<String> _productList = ["Item One", "Item Two"];
StreamController<List<String>> _products = StreamController<List<String>>();
Stream<List<String>> get products => _products.stream;
ProductBloc() {
_products.add(_productList);
}
void addProductAfterDelay() async {
_productList.add("Item Three");
await Future.delayed(const Duration(seconds: 3));
_products.add(_productList);
}
}
product_screen.dart:
StreamBuilder<List<String>>(
initialData: [],
builder: (context, snapshot) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Text(snapshot.data[index]);
});
},
);

how to make flutter searchDelagate a separate screen that can be navigated to independently?

i have a page called searchUsersSCreen which is this:
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:myApp/models/otherUser.dart';
import 'package:myApp/ui/widgets/user_profile.dart';
import 'database.dart';
class SearchUsersScreen extends StatefulWidget {
#override
_SearchUsersScreenState createState() => _SearchUsersScreenState();
}
class _SearchUsersScreenState extends State<SearchUsersScreen> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => showSearch(
context: context,
delegate: SearchUsers(
DatabaseService().fetchUsersInSearch(),
),
));
}
#override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).primaryColor,
);
}
}
and inside the same dart file is have this searchDelegate :
//Search delegate
class SearchUsers extends SearchDelegate<OtherUser> {
final Stream<QuerySnapshot> otherUser;
final String hashtagSymbol = 'assets/svgs/flaticon/hashtag_symbol.svg';
SearchUsers(this.otherUser);
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = '';
},
),
];
}
#override
Widget buildLeading(BuildContext context) {
return Container(
width: 0,
height: 0,
);
}
#override
Widget buildResults(BuildContext context) {
return Container(
width: 0,
height: 0,
color: Theme.of(context).primaryColor,
);
}
#override
Widget buildSuggestions(BuildContext context) {
showUserProfile(String userId) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UserProfileView(
userUid: userId,
)));
}
return StreamBuilder<QuerySnapshot>(
stream: DatabaseService().fetchUsersInSearch(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
final handlesResults = snapshot.data.documents
.where((u) => u['username'].contains(query));
if (!snapshot.hasData) {
return Container(
color: Theme.of(context).primaryColor,
child: Center(
child: Text(
'',
style: TextStyle(
fontSize: 16, color: Theme.of(context).primaryColor),
),
),
);
}
if (handlesResults.length > 0) {
return Container(
color: Theme.of(context).primaryColor,
child: ListView(
children: handlesResults
.map<Widget>((u) => GestureDetector(
child: Padding(
padding: const EdgeInsets.all(0.1),
child: Container(
padding: EdgeInsets.symmetric(vertical: 5),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
border: Border(
bottom: BorderSide(
width: 0.3, color: Colors.grey[50]))),
child: ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).primaryColor,
backgroundImage:
NetworkImage(u['userAvatarUrl']),
radius: 20,
),
title: Container(
padding: EdgeInsets.only(left: 10),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(u['username'],
style: TextStyle(
fontSize: 16,
color: Theme.of(context)
.accentColor),
overflow: TextOverflow.ellipsis),
SizedBox(
height: 5,
),
Text(u['name'],
style: TextStyle(
fontSize: 16,
color: Colors.grey[500],
),
overflow: TextOverflow.ellipsis),
],
),
),
trailing: Container(
padding: EdgeInsets.only(left: 10),
height: 43.0,
width: MediaQuery.of(context).size.width / 2,
),
),
),
),
onTap: () {
showUserProfile(u['id']);
},
))
.toList(),
),
);
} else {
return Container(
color: Theme.of(context).primaryColor,
child: Center(
child: Text(
'No results found',
style: TextStyle(
fontSize: 16,
color: Theme.of(context).accentColor,
),
),
),
);
}
});
}
}
i wanted to user the class SearchUsers as a separate screen that i can navigate to independently...but couldn't achieve that as SearchUsers doesn't evaluate to a widget.
so i built SearchUsersScreen statefulWidget and inside it's initState() i called this:
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => showSearch(
context: context,
delegate: SearchUsers(
DatabaseService().fetchUsersInSearch(),
),
));
}
as to make the search feature starts automatically when the user navigates to SearchUsersScreen.
and i ended up into two problems:
SearchUsers is being displayed in full screen ontop of SearchUsersSCreen (which i don't want this behavior), i want it to be displayed inside of it.
actually its covering the BottomNavigationBar i built for navigation between screens.
after SearchUsers is being displayed (and its doing its job well), when i tap the device back button...i leave SearchUsers and get back to SearchUsersScreen....which is indeed a blank screen.
so to wrap it up...all i want is to use SearchUsers class as a widget that i can navigate to and navigate from independently...thats it.
any help would be much appreciated.
thanks for your time reading.
Instead of trying to create a separate widget SearchUsers, try to create a dialog and show it when anyone wants to search users. You can also use the navigator and the back button in this case and get arguments passed from the next screen to the previous screen.

Why isn't Navigator.pop() refreshing data?

Hi guys I'm trying to build an app with flutter, so I have two screens HomeScreen() and RoutineScreen(). The first one is a Scaffold and in the body has a child Widget (a ListView called RoutinesWidget()) with all the routines. And the second one is to create a routine. The thing is, that when I create the routine, I use a button to pop to the HomeScreen() but it doesn't refresh the ListView (I'm guessing that it's because when I use Navigator.pop() it refreshes the Scaffold but not the child Widget maybe?)
HomeScreen() code here:
import 'package:flutter/material.dart';
import 'package:workout_time/constants.dart';
import 'package:workout_time/Widgets/routines_widget.dart';
import 'package:workout_time/Widgets/statistics_widget.dart';
import 'package:workout_time/Screens/settings_screen.dart';
import 'package:workout_time/Screens/routine_screen.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _selectedIndex = 0;
List<Widget> _views = [
RoutinesWidget(),
StatisticsWidget(),
];
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: kThirdColor,
appBar: AppBar(
leading: Icon(Icons.adb),
title: Text("Workout Time"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.settings),
onPressed: () => Navigator.push(context,
MaterialPageRoute(builder: (context) => SettingsScreen()))),
],
),
body: _views[_selectedIndex],
floatingActionButton: (_selectedIndex == 1)
? null
: FloatingActionButton(
onPressed: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RoutineScreen(null)));
setState(() {});
},
child: Icon(
Icons.add,
color: kSecondColor,
size: 30.0,
),
elevation: 15.0,
),
bottomNavigationBar: BottomNavigationBar(
items: <BottomNavigationBarItem>[
bottomItems(Icon(Icons.fitness_center_rounded), "Routines"),
bottomItems(Icon(Icons.leaderboard_rounded), "Statistics"),
],
currentIndex: _selectedIndex,
onTap: (int index) => setState(() => _selectedIndex = index),
),
);
}
}
BottomNavigationBarItem bottomItems(Icon icon, String label) {
return BottomNavigationBarItem(
icon: icon,
label: label,
);
}
RoutinesWidget() code here:
import 'package:flutter/material.dart';
import 'package:workout_time/Services/db_crud_service.dart';
import 'package:workout_time/Screens/routine_screen.dart';
import 'package:workout_time/constants.dart';
import 'package:workout_time/Models/routine_model.dart';
class RoutinesWidget extends StatefulWidget {
#override
_RoutinesWidgetState createState() => _RoutinesWidgetState();
}
class _RoutinesWidgetState extends State<RoutinesWidget> {
DBCRUDService helper;
#override
void initState() {
super.initState();
helper = DBCRUDService();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: helper.getRoutines(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
Routine routine = Routine.fromMap(snapshot.data[index]);
return Card(
margin: EdgeInsets.all(1.0),
child: ListTile(
leading: CircleAvatar(
child: Text(
routine.name[0],
style: TextStyle(
color: kThirdOppositeColor,
fontWeight: FontWeight.bold),
),
backgroundColor: kAccentColor,
),
title: Text(routine.name),
subtitle: Text(routine.exercises.join(",")),
trailing: IconButton(
icon: Icon(Icons.delete_rounded),
color: Colors.redAccent,
onPressed: () {
setState(() {
helper.deleteRoutine(routine.id);
});
},
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RoutineScreen(routine))),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0)),
color: kSecondColor,
);
},
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
);
}
}
RoutineScreen() code here:
import 'package:flutter/material.dart';
import 'package:workout_time/Models/routine_model.dart';
import 'package:workout_time/Widgets/type_card_widget.dart';
import 'package:workout_time/constants.dart';
import 'package:workout_time/Services/db_crud_service.dart';
class RoutineScreen extends StatefulWidget {
final Routine _routine;
RoutineScreen(this._routine);
#override
_RoutineScreenState createState() => _RoutineScreenState();
}
class _RoutineScreenState extends State<RoutineScreen> {
DBCRUDService helper;
final _nameController = TextEditingController();
final _descriptionController = TextEditingController();
bool _type = true;
int _cycles = 1;
int _restBetweenExercises = 15;
int _restBetweenCycles = 60;
#override
void initState() {
super.initState();
helper = DBCRUDService();
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
title: widget._routine != null
? Text(widget._routine.name)
: Text("Create your routine"),
actions: [
IconButton(
icon: Icon(Icons.done_rounded),
onPressed: createRoutine,
)
],
bottom: TabBar(
tabs: [
Tab(
text: "Configuration",
),
Tab(
text: "Exercises",
),
],
),
),
body: TabBarView(children: [
//_routine == null ? ConfigurationNewRoutine() : Text("WIDGET N° 1"),
ListView(
children: [
Container(
padding: EdgeInsets.all(15.0),
child: Row(
children: [
Text(
"Name:",
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
SizedBox(
width: 40.0,
),
Expanded(
child: TextField(
textAlign: TextAlign.center,
controller: _nameController,
),
),
],
),
),
SizedBox(
height: 20.0,
),
Card(
margin: EdgeInsets.all(15.0),
color: kSecondColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
child: Container(
padding: EdgeInsets.all(15.0),
child: Column(
children: [
Text(
"Type",
style: TextStyle(fontSize: 25.0),
),
Row(
children: [
Expanded(
child: TypeCard(
Icons.double_arrow_rounded,
_type == true ? kFirstColor : kThirdColor,
() => setState(() => _type = true),
"Straight set",
),
),
Expanded(
child: TypeCard(
Icons.replay_rounded,
_type == false ? kFirstColor : kThirdColor,
() => setState(() => _type = false),
"Cycle",
),
),
],
),
],
),
),
),
SizedBox(
height: 20.0,
),
Container(
padding: EdgeInsets.all(15.0),
child: Row(
children: [
Text(
"N° cycles:",
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
SizedBox(
width: 40.0,
),
Expanded(
child: Text("Hello"),
),
],
),
),
SizedBox(
height: 20.0,
),
],
),
Text("WIDGET N° 2"),
]),
),
);
}
void createRoutine() {
List<String> _exercises = ["1", "2"];
List<String> _types = ["t", "r"];
List<String> _quantities = ["30", "20"];
Routine routine = Routine({
'name': _nameController.text,
'description': "_description",
'type': _type.toString(),
'cycles': 1,
'numberExercises': 2,
'restBetweenExercises': 15,
'restBetweenCycles': 60,
'exercises': _exercises,
'types': _types,
'quantities': _quantities,
});
setState(() {
helper.createRoutine(routine);
Navigator.pop(context);
});
}
}
Any idea what can I do to make it work? Thank you
Make it simple
use Navigator.pop() twice
so that the current class and old class in also removed
from the stack
and then use Navigator.push()
When you push a new Route, the old one still stays in the stack. The new route just overlaps the old one and forms like a layer above the old one. Then when you pop the new route, it will just remove the layer(new route) and the old route will be displayed as it was before.
Now you must be aware the Navigator.push() is an asynchronous method and returns a Future. How it works is basically when you perform a Navigator.push(), it will push the new route and will wait for it to be popped out. Then when the new route is popped, it returns a value to the old one and that when the future callback will be executed.
Hence the solution you are looking for is add a future callback like this after your Navigator.push() :
Navigator.push(context,
MaterialPageRoute(builder: (context) => SettingsScreen())
).then((value){setState(() {});}); /// A callback which is executed after the new route will be popped. In that callback, you simply call a setState and refresh the page.