Can someone explain me why the productlist variable is null when this class run? It loooks like the variable is being populated after the first istance end, so the CardShopList widget hasn't the productlist that is coming from the nested istance.
Have you got some tips? Thanks!
Future getMyShopLists(User user) async {
List<Widget> listItems = [];
FirebaseFirestore.instance
.collection('user')
.doc(user.uid)
.collection('shoplist')
.get()
.then((event) {
event.docs.forEach((shoplist) {
List<ProductModel> productlist = [];
Map shopListData = shoplist.data();
shoplist.reference.collection('productlist').get().then((element) {
Map productData = shoplist.data();
element.docs.forEach((doc) {
productlist.add(ProductModel(
id: doc.id,
name: productData['name'],
quantity: productData['quantity'],
price: productData['price'],
));
});
});
listItems.add(CardShopList(
shoplist.id,
shopListData['name'],
productlist, // <------------------------------- THIS IS NULL!!!!
getMyShopLists,
key: ValueKey<String>(shoplist.id),
));
});
if (this.mounted) {
setState(() {
shopListsWidget = listItems;
});
}
});
return shopListsWidget;
}
EDIT i use that class in this FutureBuilder :
FutureBuilder(
future:
searchIcon.icon == Icons.search ? getMyShopLists(user) : null,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return isShopListEmpty
? new Center(
child: new Text(
"x",
style: TextStyle(fontSize: 20),
textAlign: TextAlign.center,
),
)
: Container(
child: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: shopListsWidget.length,
physics: BouncingScrollPhysics(),
itemBuilder: (context, index) {
return shopListsWidget[index];
}))
],
),
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
The "productlist" is not expected to be filled because you are using foreach. There may be alternative solutions, but turning foreach to for will probably fix it.
...
await FirebaseFirestore.instance //added await
.collection('user')
.doc(user.uid)
.collection('shoplist')
.get()
.then((event) async{ //added async
for(QueryDocumentSnapshot shoplist in event.docs){ //---this line changed
List<ProductModel> productlist = [];
Map shopListData = shoplist.data();
await shoplist.reference.collection('productlist').get().then((element) //added await
{
Map productData = shoplist.data();
for(QueryDocumentSnapshot doc in element.docs) //---this line changed
{
productlist.add(ProductModel(
id: doc.id,
name: productData['name'],
quantity: productData['quantity'],
price: productData['price'],
));
}
});
listItems.add(CardShopList(
shoplist.id,
shopListData['name'],
productlist,
getMyShopLists,
key: ValueKey<String>(shoplist.id),
));
}
...
Keep in mind I did not tested this, but I had previous issues with that.
My suggestion is to move this block of code:
listItems.add(CardShopList(
shoplist.id,
shopListData['name'],
productlist, // <------------------------------- THIS IS NULL!!!!
getMyShopLists,
key: ValueKey<String>(shoplist.id),
));
Inside the firebase query like so:
shoplist.reference.collection('productlist').get().then((element) {
Map productData = shoplist.data();
element.docs.forEach((doc) {
productlist.add(ProductModel(
id: doc.id,
name: productData['name'],
quantity: productData['quantity'],
price: productData['price'],
));
});
listItems.add(CardShopList(
shoplist.id,
shopListData['name'],
productlist,
getMyShopLists,
key: ValueKey<String>(shoplist.id),
));
});
The reason for the null value is that the firebase query has a local variables and they exist only for the moment of the request. The only way to work with them is locally in the query callback.
You're defining it as an async function, but didn't await for the queries to be processed in order, can you try adding await in these locations:
//here #1
await FirebaseFirestore.instance
.collection('user')
.doc(user.uid)
.collection('shoplist')
.get()
.then((event) async {
event.docs.forEach((shoplist) {
List<ProductModel> productlist = [];
Map shopListData = shoplist.data();
//here#2
await shoplist.reference.collection('productlist').get().then((element) {
Map productData = shoplist.data();
element.docs.forEach((doc) {
productlist.add(ProductModel(
id: doc.id,
name: productData['name'],
quantity: productData['quantity'],
price: productData['price'],
));
});
});
Related
I'm creating an grocery app in Flutter with firebase but i am unable show the updated cart value to text widget.
This is my List view
var fire_storedb = FirebaseFirestore.instance.collection("vegetables").snapshots();
Container(
margin: const EdgeInsets.only(top: 2),
alignment: Alignment.center,
child: StreamBuilder(
stream: fire_storedb,
builder: ((context, snapshot) {
if (!snapshot.hasData) return const CircularProgressIndicator();
return (ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
return (grocery_list(snapshot, index, context,values45));
},
));
})),
),
Below is my grocery_list function which is called from ListView ........
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
InkWell(
child: Icon(FontAwesomeIcons.minus),
onTap: () async {
String? grocery_id =
snapshot.data?.docs[index].reference.id;
FirebaseFirestore.instance
.collection("Cart")
.where("grocery_id", isEqualTo: grocery_id)
.get()
.then((value) {
value.docs.forEach((element) {
FirebaseFirestore.instance
.collection("Cart")
.doc(element.id)
.delete()
.then((value) {
print("Success!");
});
});
});
}), //Inkwell for delete item from cart
VerticalDivider(width: 10,), ////// Vertical Divider
VerticalDivider(width: 10,), ////// Vertical Divider
InkWell(
child: Icon(FontAwesomeIcons.plus),
onTap: () async {
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
var email =
sharedPreferences.getString("email").toString();
String? docid =
snapshot.data?.docs[index].reference.id;
Map<String, String?> data_to_save = {
"grocery_id": docid,
"quantity": "1",
"email": email,
"name": snapshot.data!.docs[index]['name'],
"price": snapshot.data!.docs[index]['price'],
"si": snapshot.data!.docs[index]['si'],
"image": snapshot.data!.docs[index]['image'],
};
var collectionRef = await FirebaseFirestore.instance
.collection("Cart");
collectionRef.add(data_to_save);
},
), // Inkwell for add item to cart
],
),
I want to place the below code between the two vertical divider as a text wideget to show the no of items added to cart. Can someone help.? I'm able to get the cart value in cart_value but unable to display it to Text widget.
FirebaseFirestore.instance.collection("Cart").get().then((value) {
value.docs.forEach((element) {
FirebaseFirestore.instance.collection("Cart").doc(element.id).get().then((value2) => {
if(value2.data()!['grocery_id']==docid)
cart_value = (value2.data()['quantity'])
});
});
});
You should be using a Future method to fetch data from Firestore and return an integer or double value of cart_value like this :
int cart_value = 0;
Future<int> cart() async {
var cart = await FirebaseFirestore.instance.collection("Cart").get();
for (var element in cart.docs) {
FirebaseFirestore.instance.collection("Cart").doc(element.id).get().then((value2) => {
if(value2.data()!['grocery_id']==docid){
cart_value = (value2.data()!['quantity'])
}
});
}
return cart_value;
}
and put the Future method cart in the future of your FutureBuilder widget:
FutureBuilder(
future: cart(),
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.toString());
}})
I am developing an android application with flutter, what I am doing is displaying a json in an application page. When I run the application it doesn't give me an error but it doesn't show me the data, the json I want to read is the following:
[
{
"deviceid": 27,
"f2cuid": "Estacion1_A",
"devicedata": {
"isRunning": 0,
"color": "w3-red",
"colorNoW3": "red",
"device_name": "Estacion1_A"
}
},
{
"deviceid": 20,
"f2cuid": "B19",
"devicedata": {
"isRunning": 1,
"color": "w3-green",
"colorNoW3": "green",
"device_name": "B19"
}
}
]
It's in my model class:
class Stations {
Int? isRunning;
String? color;
String? colorNoW3;
String? devicename;
Stations(
{this.isRunning,
this.color,
this.colorNoW3,
this.devicename,
});
factory Stations.fromJson(Map<String, dynamic> json) {
return Stations(
isRunning: json['isRunning'],
color: json['color'],
colorNoW3: json['colorNoW3'],
devicename: json['device_name'],
);
}
}
This is my service:
Future<List<Stations>> getStations() async {
Uri url = Uri.parse('URL');
final response = await http.get(url);
var data = jsonDecode(response.body);
print('data: $data');
List<Stations> stations = data.map((e) => Stations.fromJson(e)).toList();
return stations;
}
and this is the way I display it:
return Scaffold(
appBar: AppBar(
title: const Text('Sistema Escolar Administrativo'),
),
drawer: DrawerWidgetMenu(),
body: Container(
child: FutureBuilder(
future: stationSvc.getStations(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Container(
child: Center(
child: Text('No hay datos que mostrar'),
),
);
}
return snapshot.data.length > 0
? ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: InkWell(
onTap: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => HomePage(),
),
);
},
child: ListTile(
leading: Text(snapshot.data[index].devicename!),
title: Text(snapshot.data[index].color!),
subtitle: Text(snapshot.data[index].colorNoW3!),
),
));
})
: Center(
child: Text('No hay datos, registra un grupo primero'));
}),
),
);
You forgot to specify nested map:
factory Stations.fromJson(Map<String, dynamic> json) {
return Stations(
isRunning: json['devicedata']?['isRunning'],
color: json['devicedata']?['color'],
colorNoW3: json['devicedata']?['colorNoW3'],
devicename: json['devicedata']?['device_name'],
);
}
I am sharing a complete class named SampleModel below which can help to parse JSON in flutter:
class SampleModel {
String? _myName;
bool? _isActive;
SampleModel({String? myName, bool? isActive}) {
if (myName != null) {
_myName = myName;
}
if (isActive != null) {
_isActive = isActive;
}
}
String? get myName => _myName;
set myName(String? myName) => _myName = myName;
bool? get isActive => _isActive;
set isActive(bool? isActive) => _isActive = isActive;
SampleModel.fromJson(Map<String, dynamic> json) {
_myName = json['myName'];
_isActive = json['isActive'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['myName'] = _myName;
data['isActive'] = _isActive;
return data;
}
}
Sample JSON for the same class:
{
"myName" : "Your Name",
"isActive" : true
}
Check if this can help in your case.
your json type is an array, not a map.
look at [ ] syntax on json file.
to deal with an Array, you should make it to a list first:
List<Map<String,dynamic>> mylistofMapformJson = json.decode(receivedJson);
//you should get your list stations like this:
List<Stations> listStationsFromJson = List.generate(
mylistofMapformJson.length,
(index) => Stations.fromJson(mylistofMapformJson));
I want to make a to-do list with task due date as an optional field, so I need to check if some tasks have dueDate and add it as a subtitle based on that. How can I check if a field exists inside a doc in a StreamBuilder?
class _TaskListState extends State<TaskList> {
var myStream;
#override
void initState() {
myStream = FirebaseFirestore.instance
.collection('tasks')
.doc(widget.uid)
.collection('mytasks')
.snapshots();
super.initState();
}
...
void _updateTaskDesc(
dynamic currTask, String newDesc, DateTime newDate, TimeOfDay newTime) {
FirebaseFirestore.instance
.collection('tasks')
.doc(widget.uid)
.collection('mytasks')
.doc(currTask['id'])
.update({
'desc': newDesc,
'dueDate': newDate.toString(),
'dueTime': newTime.toString(),
});
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: myStream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: SizedBox(
height: 100, width: 100, child: CircularProgressIndicator()),
);
} else {
final docs = snapshot.data.docs;
bool hasDateTime = ????? <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
return ListView.builder(
itemCount: docs.length,
itemBuilder: (ctx, index) {
final currTask = docs[index];
return InkWell(
highlightColor: Theme.of(context).secondaryHeaderColor,
splashColor: Theme.of(context).secondaryHeaderColor,
onLongPress: () {
showModalBottomSheet<dynamic>(
isScrollControlled: true,
context: context,
builder: (bCtx) {
FocusManager.instance.primaryFocus?.unfocus();
return TaskOptions(_updateTaskDesc,
() => _updateHasImage(docs[index]), currTask);
},
);
},
child: Dismissible(
direction: DismissDirection.startToEnd,
key: UniqueKey(),
onDismissed: (_) async {
FirebaseFirestore.instance
.collection('tasks')
.doc(widget.uid)
.collection('mytasks')
.doc(currTask['id'])
.delete();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("${currTask['desc']} dismissed"),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
FirebaseFirestore.instance
.collection("tasks")
.doc(widget.uid)
.collection("mytasks")
.doc(currTask['id'])
.set({
"desc": currTask['desc'],
"id": currTask['id'],
"isDone": currTask['isDone'],
"hasImage": currTask['hasImage'],
});
try {
FirebaseFirestore.instance
.collection("tasks")
.doc(widget.uid)
.collection("mytasks")
.doc(currTask['id'])
.update({
"dueDate": currTask['dueDate'],
"dueTime": currTask['dueTime'],
});
} catch (e) {}
},
),
),
);
},
child: ListTile(
...
subtitle: Text(hasDateTime
? DateFormat('dd/MM')
.format(DateTime.parse(currTask['dueDate']))
: ''),
...
I saw that a containsKey('key') method works for some people but I get NoSuchMethod when I try that. What can I do?
The single document is just a normal Dart Map, so you can check if a key exists or not using containsKey method.
So you condition becomes the following:
bool hasDateTime = currTask.containsKey('dueDate`);
NOTE: In the question I can see that you are defining the condition in the wrong place which is outside the itemBuilder method in the ListView so that it is not item based and well not work because it does not make sense.
You can have it in this place:
...
itemBuilder: (ctx, index) {
final currTask = docs[index];
bool hasDateTime = currTask.containsKey('dueDate`);
return InkWell(
...
I am able to fetch and print the data in json format in console and I am able to display the entire body of data in list format in the flutter app. But I can't figure out how to display only a specific key and its value in list. Its a list of maps. I have removed the consumer key and secret from this code.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main(){
runApp(MaterialApp(
home: CustomHome(),
));
}
class CustomHome extends StatefulWidget {
#override
_CustomHomeState createState() => _CustomHomeState();
}
class _CustomHomeState extends State<CustomHome> {
List data;
Future<String> getData() async{
var response = await http.get('https://jbaat.com/wp-json/wc/v3/products/?consumer_key=&consumer_secret=');
setState(() {
var converted = jsonDecode(response.body);
data = converted;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
this.getData();
}
#override
Widget build(BuildContext context) {
print(data);
return Scaffold(
body: ListView.builder(
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, int index) {
return Container(
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text('$data'),
),
),
);
}),
);
}
}
Below is the response
[{id: 493, name: Bas 5 Min White Half Sleeve T-Shirt, slug: bas-5-min-white-half-sleeve-t-shirt, permalink: https://www.jbaat.com/product/bas-5-min-white-half-sleeve-t-shirt/, date_created: 2019-12-14T23:39:08, date_created_gmt: 2019-12-14T18:09:08, date_modified: 2019-12-14T23:48:01, date_modified_gmt: 2019-12-14T18:18:01, type: variable, status: publish, featured: false, catalog_visibility: visible, description: , short_description: , sku: , price: 500.00, regular_price: , sale_price: , date_on_sale_from: null, date_on_sale_from_gmt: null, date_on_sale_to: null, date_on_sale_to_gmt: null, price_html: <span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">₹</span>500.00</span>, on_sale: false, purchasable: true, total_sales: 0, virtual: false, downloadable: false, downloads: [], download_limit: -1, download_expiry: -1, external_url: , button_text: , tax_status: taxable, tax_class: , manage_stock: false, stock_quantity: null, stock_status: instock, backorders: no,
Jbaat, I'd recommend creating a model for your response data and use the values accordingly from each item's instance. There are few online converters available which converts your json response to Dart models, here is one - https://javiercbk.github.io/json_to_dart/. Below is a quick example of what it would look like based on your response data,
class Items {
final List<Items> items;
Items({this.items});
factory Items.fromJson(List<dynamic> json) {
List<Items> itemList = json.map((i) => Items.fromJson(i)).toList();
return Items(
items: itemList
);
}
}
class Item {
final int id;
final String name;
......
Item({this.id, this.name,.....});
factory Item.fromJson(Map<String, dynamic> json) {
return Item(
id: json['id'],
name: json['name']
......
);
}
}
And then your getData() would be,
Future<Items> getData() async{
var response = await http.get('https://jbaat.com/wp-json/wc/v3/products/?consumer_key=&consumer_secret=');
return Items.fromJson(json.decode(response.body)).items;
}
You should now have Item list which can be used to get specific item info. You should also use FutureBuilder to call your getData() instead of calling it in initState to make sure data is available before building widgets like so,
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Scaffold(
body: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
Item item = snapshot.data[index]; //Your item
return Container(
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(item.name),
),
),
);
}),
);
} else {
return Center(child: CircularProgressIndicator());
}
});
}
Hope this helps. Good luck!
Change the type for data to
List<Map<String,dynamic>>
A possible implementation for your use case:
Map y;
var keytobesearched='name';
List<Map<String,dynamic>> x= [{'id': 493, 'name': 'Bas 5 Min White Half Sleeve T-Shirt', 'slug': 'bas-5-min-white-half-sleeve-t-shirt'}];
x.forEach((Map<String,dynamic> ele){
if(ele.containsKey(keytobesearched))
y=Map.from(ele);
// display/alter y to your liking
});
If you would want a complete plug and play Woocommerce Sdk that handles authentication, products, customer, shipping etc for you, you can use the Woo Commerce SDK library for flutter at https://pub.dev/packages/woocommerce
Woocommerce myWoocommerce = WooCommerce(baseUrl: yourBaseUrl, consumerKey: yourConsumerKey, consumerSecret: consumerSecret);
Then simply get your lists eg:
WooProduct products = await myWocommerce.getProducts(); // returns the ist of products.
for (var product in products){ print(product.name)};
I want to access all the reg_events for the currently logged in user. I have the following code right now
stream: Firestore.instance.collection('users').document(email).snapshots(),
builder: (context, snapshot){
if(!snapshot.hasData){
return Text("Loading..");
}
return Center(
child: new Container(
child: new PageView.builder(
onPageChanged: (value) {
setState(() {
currentpage = value;
});
},
controller: controller,
itemCount: snapshot.data['reg_events'].length,
itemBuilder: (context, index) => builder(index, snapshot.data)),
),
);
}
),
The 'builder' is:
builder(int index, DocumentSnapshot document) {
return new AnimatedBuilder(
animation: controller,
builder: (context, child) {
double value = 1.0;
if (controller.position.haveDimensions) {
value = controller.page - index;
value = (1 - (value.abs() * .5)).clamp(0.0, 1.0);
}
return new Center(
child: new SizedBox(
height: Curves.easeOut.transform(value) * 200,
width: Curves.easeOut.transform(value) * 1000,
child: child,
),
);
},
child: new Card(
child: Text(document.data['reg_events'][0].toString(),
textAlign: TextAlign.center,
style: TextStyle(fontSize: 15, color: Colors.white),),
margin: const EdgeInsets.all(10.0),
color: index % 2 == 0 ? Colors.blue : Colors.red,
),
);
}
But it outputs "Instance of 'DocumentReference'". How do I access this Document Reference?
Maybe it not understand reg_event as List so try this,
stream: Firestore.instance.collection('users').document(email).snapshots(),
builder: (context, snapshot){
List regEvent = new List();
if(snapshot.hasData){
regEvent = snapshot.data['reg_events'];
}
if(!snapshot.hasData){
return Text("Loading..");
}
return Center(
child: new Container(
child: new PageView.builder(
onPageChanged: (value) {
setState(() {
currentpage = value;
});
},
controller: controller,
itemCount: regEvent.length,
itemBuilder: (context, index) {
print(regEvent[index]);
return builder(index, snapshot.data)),}
),
);
}
),
DocumentReference is like a pointer to one document. You can get the single document using .get method which returns Future<DocumentSnapshot>. Since you have an array of them, you can then use Streams to get a bunch of Futures.
List<DocumentReference> references = [ref, ref, ref];
var myStream = Stream.fromFutures(references.map((ref) => ref.get()).toList());
StreamBuilder(builder: ..., stream: myStream);
But...
Firestore has querying, so it should be better if you actually use it. You should be able to reference your reg_events like that:
Firestore.instance.collection('users').document("$email/reg_events").snapshots();
In this example a User object is created which contains a list of references of the entities (or events). This list is then passed to the DatabaseService class which returns a list of EntityDetails stream objects.
DatabaseService Class:
final CollectionReference entityCollection =
Firestore.instance.collection('entities');
final CollectionReference userCollection =
Firestore.instance.collection('user');
Stream<UserDetails> get userDetails {
return userCollection
.document(uid)
.snapshots()
.map(_userDetailsFromSnapshot);
}
UserDetails _userDetailsFromSnapshot(DocumentSnapshot snapshot) {
return UserDetails(
email: snapshot.data['email'],
phone: snapshot.data['phone'],
fname: snapshot.data['fname'],
lname: snapshot.data['lname'],
streetNr: snapshot.data['street_nr'],
city: snapshot.data['city'],
entities: List.from(snapshot.data['entities']));
}
List<Stream<EntityDetails>> getEntitiesFromDRList(
List<DocumentReference> entities) {
List<Stream<EntityDetails>> elist = new List();
entities.forEach((element) {
elist.add(element.snapshots().map((_entityDetailsFromSnapshot)));
});
return elist;
}
EntityDetails _entityDetailsFromSnapshot(DocumentSnapshot snapshot) {
return EntityDetails(
uid: uid,
name: snapshot.data['name'],
description: snapshot.data['description'],
type: snapshot.data['type'],
geoPoint: snapshot.data['geopoint'],
adressString: snapshot.data['adress_string'],
email: snapshot.data['email'],
phone: snapshot.data['phone'],
);}
Widget
stream: DatabaseService(uid: uid).userDetails,
builder: (context, snapshot) {
if (snapshot.hasData) {
UserDetails userDetails = snapshot.data;
//Get the Streams
DatabaseService(uid: uid)
.getEntitiesFromDRList(userDetails.entities)
.forEach((element) {
element.listen((data) {
print("DataReceived: " + data.name);
}, onDone: () {
print("Task Done");
}, onError: (error) {
print("Some Error");
});
});
User-Object
class UserDetails {
final String email;
final String phone;
final String fname;
final String lname;
final String streetNr;
final String city;
final List<DocumentReference> entities;
UserDetails(
{this.email,
this.phone,
this.fname,
this.lname,
this.streetNr,
this.city,
this.entities});
}