How to load data from Firestore using keys - flutter

I'm trying to build an e-commerce app using Flutter and Firestore, I'm having a challenge in building the cart. using the codes below, I have been able to get the products a user wishes to add to the cart using the product id. My challenge is how to use the id or the keys to fetch the details of the products from Firestore and store the cart products either in Firestore or SQLite so that I can query from there and display them on the cart page.
appstate.dart
class AppStateModel extends Model {
final Map<int, int> _productsInCart = <int, int>{};
Map<int, int> get productsInCart => Map<int, int>.from(_productsInCart);
void addProductToCart(int productId) {
if (!_productsInCart.containsKey(productId)) {
_productsInCart[productId] = 1;
} else {
_productsInCart[productId]++;
}
notifyListeners();
}
void removeItemFromCart(int productId) {
if (_productsInCart.containsKey(productId)) {
if (_productsInCart[productId] == 1) {
_productsInCart.remove(productId);
} else {
_productsInCart[productId]--;
}
}
notifyListeners();
}
void clearCart() {
_productsInCart.clear();
notifyListeners();
}
}
product_display.dart
page with onPressed function to get the id of the item clicked to add to cart
CupertinoActionSheetAction(
child: const Text('Add To Cart'),
onPressed: () {
model.addProductToCart(products[index].id);
},
)
product.dart
class Products{
final String category;
final String description;
final int id;
final int price;
final String title;
final String url;
const Products( {this.category,this.description,this.id, this.price, this.title, this.url,
});
}
CartProduct.dart
class CartProducts{
final String category;
final String description;
final int id;
final int price;
final String title;
final String url;
const CartProducts( {this.category,this.description,this.id, this.price, this.title, this.url,
});
}
Now let say I have products with ids 1, 4, 6, 9, 11 in product cart, when I print in the console using print(model.productsInCart.keys), this was the output (1, 4, 6, 9, 11), now my challenge is how to use these ids to query the products with ids 1,4,6,9,11 from Firestore collection products and store them either in Firebase or SQLite so that I can display them in the cart page for a user to view his/her items in cart.

I think this is what you are trying to do is
Firestore.instance.collection("collection").document("id").get().then((querySnapshot){
print(querySnapshot.data);
});
Obviously replace collection with your collection name and id with the id you are trying to fetch. Here I am using .then syntax, but you can pass everything before .then to the FutureBuilder as usual.
Edit:
You'll need to add a helper method for fetching all the data from the firestore.
Future<List<Products>> getCollection() async {
List<int> idList = [];
// productsInCart[key] = value; where key is id and value is amount in cart
productsInCart.forEach((key, value) {
idList.add(key);
});
List<Products> productList = [];
for (var id in idList) {
var documents = (await Firestore.instance
.collection('products')
.where('id', isEqualTo: id)
.getDocuments())
.documents;
if (documents.length > 0) {
var doc = documents[0]
.data; // if there are multiple documents with given id get first document
var prod = Products(
id: doc['id'],
title: doc['title'],
price: doc['price'],
category: doc['category'],
description: doc['description']);
productList.add(prod);
}
}
return productList;
}
then use FutureBuilder to build the list
FutureBuilder<List<Products>>(
future: getCollection(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var list = snapshot.data;
return ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) => ListTile(
title: Text("${list[index].title}"),
subtitle: Text(
"Amount in cart : ${productsInCart[list[index].id]}"),
));
} else {
return Text("");
}
},
);
I am using Future and FutureBuilder instead of Stream and StreamBuilder, because having to query using multiple ids is a tedious task in cloud firestore, since firestore doesn't have official support for Logical OR. So having to gather data from multiple stream sources is difficult. Using FutureBuilder has the same output as using StreamBuilder as long as product detail is not being changed while app is being used.
Edit 2:
To use multiple stream sources use StreamGroup from async package. Here is what final code looks like
Stream<List<Products>> _getCollection() async* {
List<int> idList = [];
// productsInCart[key] = value; where key is id and value is amount in cart
productsInCart.forEach((key, value) {
idList.add(key);
});
StreamGroup<QuerySnapshot> streamGroup = StreamGroup();
for (var id in idList) {
var stream = Firestore.instance
.collection('products')
.where('id', isEqualTo: id)
.snapshots();
streamGroup.add(stream);
}
//using map to store productDetails so that same products from multiple stream events don't get added multiple times.
Map<int, Products> productMap = {};
await for (var val in streamGroup.stream) {
var documents = val.documents;
var doc = documents[0].data;
var product = Products.fromMap(doc);
productMap[product.id] = product;
yield productMap.values.toList();
}
}
StreamBuilder<List<Products>>(
stream: _getCollection(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var values = snapshot.data;
return ListView.builder(
itemCount: values.length,
itemBuilder: (context, index) => ListTile(
title: Text(values[index].title),
subtitle: Text(
"Amount in cart : ${productsInCart[values[index].id]}"),
));
} else {
print("There is no data");
return Text("");
}
},
),
I've added named constructor for convenience
Products.fromMap(Map map) {
this.id = map['id'];
this.title = map['title'];
this.description = map['description'];
this.price = map['price'];
this.category = map['category'];
}

Related

How do I read data with stream builder correctly. I keep getting blank page

I am stuck at trying to read the current user's data after storing it in Firestore. The page keeps showing a blank page instead of showing the actual data from Firestore.
I have created a model for the data like this
class Vital {
String id;
final String bloodSugar;
final String bloodPressure;
final String bodyTemp;
final DateTime? createdOn;
Vital({
this.id = '',
required this.bloodSugar,
required this.bloodPressure,
required this.bodyTemp,
required this.createdOn,
});
Map<String, dynamic> toJson() => {
'id': id,
'bloodSugar': bloodSugar,
'bloodPressure': bloodPressure,
'bodyTemp': bodyTemp,
"createdOn": Utils.fromDateTimeToJson(createdOn)
};
Vital.fromSnapShot(DocumentSnapshot<Map<String, dynamic>> snapshot)
: id = snapshot.id,
bloodSugar = snapshot.data()!["bloodSugar"],
bloodPressure = snapshot.data()!["bloodPressure"],
bodyTemp = snapshot.data()!["bodyTemp"],
createdOn = snapshot.data()!["createdOn"].toDate();
}
I have stored the data in Firestore using the below code
Future addVitals() async {
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = await auth.currentUser;
final uid = user?.uid;
final vitals = FirebaseFirestore.instance
.collection('vitalsign')
.doc(uid)
.collection("usersVitals");
final vital = Vital(
id: vitals.id,
createdOn: DateTime.now(),
bloodSugar: _bloodSugar.text,
bloodPressure: _bloodPressure.text,
bodyTemp: _bodyTemp.text);
final json = vital.toJson();
await vitals.add(json);
Navigator.push(
context, MaterialPageRoute(builder: (context) => VitalsSaved()));
}
Please note that users will be allowed to add their vitals everyday and what I want to achieve is to get the vitals of the current users in a separate page using stream builder.
This is what I would like to achieve
blood pressure
day1 day2
120/70 120/65 etc
blood glucose
day1 day2
27 26 etc
this will be for a specific user and once another user login to the app, they should only get their own data from what they put in the input.
This is what I have tried to do in the stream of the stream builder
Stream<QuerySnapshot> readVitals() async* {
final FirebaseAuth auth = FirebaseAuth.instance;
final user = auth.currentUser;
final uid = user?.uid;
yield* FirebaseFirestore.instance
.collection('vitalsign')
.doc(uid)
.collection("userVitals")
.snapshots();
}
then I created a widget as follow to get data from firestore
Widget buildPressure(BuildContext context, DocumentSnapshot document) {
return Container(
child: Card(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Text(
document["bloodSugar"],
style: TextStyle(fontSize: 20),
)
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [Text(document["bloodPressure"])],
),
),
],
),
),
);
}
Then lastly I use my widget in the streanbuilder as follow
stream: readVitals(),
builder: (context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return const Text("Loading...");
}
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (BuildContext context, int index) {
return buildPressure(context, snapshot.data.docs[index]);
});
}
return CircularProgressIndicator();
},
)
After everything I still get a blank page which means that I am not getting any data from Firestore despite having create data and add it in Firestore.
I have tried several ways but could not get it.
I think you don't need to put .data() in = snapshot.data()! in model class part.
I will give you a simple example from code of my project that may help you move on..
class ReadsModel {
late String id;
late String firstName;
late String lastName;
late String father;
late String mother;
late String readDate;
late String subType;
late String counterNumber;
late String paymentStatus;
late int counterValue;
ReadsModel({
required this.id,
required this.firstName,
required this.lastName,
required this.father,
required this.mother,
required this.readDate,
required this.subType,
required this.counterNumber,
required this.paymentStatus,
required this.counterValue,
});
ReadsModel.fromMap(DocumentSnapshot data){
id = data.id;
firstName = data['FirstName'];
lastName = data['LastName'];
father = data['Father'];
mother = data['Mother'];
readDate = data['ReadDate'];
subType = data['Subscription type'];
counterNumber = data['CounterNumber'];
paymentStatus = data['Payment Status'];
counterValue = data['CounterValue'];
}
}
For the saving to Firestore part:
Future savingToFireStore() async {
await villagesCollectionRef
.doc()
.collection('Reads')
.doc(const Uuid().v4())
.set({
'FirstName': firstName,
'LastName': lastName,
'Father': father,
'Mother': mother,
'ReadDate': readDate,
'Subscription type': subType,
'Payment Status': 'unPaid',
'CounterValue': int.parse(readValueController.text),
'CounterNumber': counterNumber,
});
}
Then I create a list from the model type to store data in:
List<VillageModel> areaModel = <VillageModel>[].obs;
Then use this function to from GetX package to stream data and get it "You can use Stream builder in the same way"
getReads() {
return villagesCollectionRef
.doc()
.collection('Reads')
.snapshots()
.listen((event) {
readsModel.clear();
for (var element in event.docs) {
readsModel.add(ReadsModel.fromMap(element));
}
});
}
Finally, in the UI part you can use the suitable widget to show data, for me I used ListView.builder().
I hope that helps you.
Try changing your:
Stream<QuerySnapshot> readVitals() async* {
final FirebaseAuth auth = FirebaseAuth.instance;
final user = auth.currentUser;
final uid = user?.uid;
yield* FirebaseFirestore.instance
.collection('vitalsign')
.doc(uid)
.collection("userVitals")
.snapshots();
}
into:
Stream<Iterable<Vital>> readVitals() async* {
final FirebaseAuth auth = FirebaseAuth.instance;
final user = auth.currentUser;
final uid = user?.uid;
yield* FirebaseFirestore.instance
.collection('vitalsign')
.doc(uid)
.collection("userVitals")
.snapshots().map((event) =>
event.docs
.map((doc) => Vital.fromSnapshot(doc)));
}
This way you should return the values of the documents in the collection UserVitals. Note that if this documents don't have all the fields (bloodSugar, bloodPressure, bodyTemp and createdOn) you are probably gonna get an error.

Flutter Firebase Iterate through all documents that matches field value and inserting field from each one into a List of String

I'm trying to get afield value into a list of string from documents in the firebase that match the WHERE of a field value from a value of my own.
(I wanna show images above each other to create a full picture depending on the data, using two fields to be exact and then creating a list of images to show as positioned inside a Stack)
my code:
Implant Class:
class Implant {
final String id;
final String pid;
final String aptid;
late String type;
late double? sizew;
late int? sizel;
late String? positionQ;
late int? positionN;
Implant(this.id, this.pid, this.aptid, this.type, this.sizew, this.sizel,
this.positionQ, this.positionN);
Map<String, dynamic> toJson() => {
'ID': id,
'PID': pid,
'APTID': aptid,
'TYPE': type,
'SIZEW': sizew,
'SIZEL': sizel,
'POSITIONQ': positionQ,
'POSITIONN': positionN,
};
factory Implant.fromJson(Map<String, dynamic> json) {
return Implant(
json['ID'],
json['PID'],
json['APTID'],
json['TYPE'],
json['SIZEW'],
json['SIZEL'],
json['POSITIONQ'],
json['POSITIONN'],
);
}
static Map<String, dynamic> toMap(Implant implant) => {
'ID': implant.id,
'PID': implant.pid,
'APTID': implant.aptid,
'TYPE': implant.type,
'SIZEW': implant.sizew,
'SIZEL': implant.sizel,
'POSITIONQ': implant.positionQ,
'POSITIONN': implant.positionN,
};
static String encode(List<Implant> implant) => json.encode(
implant
.map<Map<String, dynamic>>((implant) => Implant.toMap(implant))
.toList(),
);
static List<Implant> decode(String implants) =>
(json.decode(implants) as List<dynamic>)
.map<Implant>((implant) => Implant.fromJson(implant))
.toList();
}
Future of list of string function:
static Future<List<String>> getImplants(Patient patient) async {
List<String> result = ['assets/images/teeth/empty.png'];
var collection = FirebaseFirestore.instance.collection('implants');
var docSnapshot = await collection.doc(patient.id).get();
if (docSnapshot.exists) {
Map<String, dynamic> data = docSnapshot.data()!;
result.add(data['POSITIONQ'] + data['POSITIONN']);
}
return result;
}
How I translate them into Stack and Positioned:
static Future<void> showPatientImplants(BuildContext context, Patient patient,
{bool spaces = true}) async {
List<String> myImplants;
myImplants = await getImplants(patient);
List<Widget> x = [Image.asset('assets/images/teeth/empty.png')];
myImplants.forEach((element) {
x.add(Image.asset(element));
});
return await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
scrollable: true,
content: SingleChildScrollView(
child: GestureDetector(
onTap: () {
log(myImplants.toString());
},
child: Stack(
children: x,
),
),
),
);
});
}
The only problem I'm facing -I think- is that the Future<List> function doesn't get me the values I need.
I tried using other functions I found on SO and google, nothing worked.
I guess I can try using the stream and Listview.builder just to get the files names but it seems like exactly how it shouldn't be done.
any help is appreciated.
Never mind Solved it using the following code:
static Future<List<String>> getImplants(Patient patient) async {
List<String> result = ['assets/images/teeth/empty.png'];
log('ID:${patient.id}');
var collection = FirebaseFirestore.instance
.collection('implants')
.where('PID', isEqualTo: patient.id);
var docSnapshot = await collection.get();
for (var element in docSnapshot.docs) {
if (element.exists) {
Map<String, dynamic> data = element.data();
result.add(data['POSITIONQ'].toString() + data['POSITIONN'].toString());
}
}
return result;
}
but if anyone has a better idea I appreciate it along with everyone who has a similar problem.

The getter 'length' was called on null. Receiver: null Tried calling: length FutureBuilder with List

I keep getting the error mentioned above during runtime of my flutter app. Basically what I am trying to achieve is to fetch data from an api and display it in a form of a SliverList by using a FutureBuilder.
This was working perfectly fine until I changed my code for the list from FutureBuilder<List> to FutureBuilder<List> to make use of the class EntertainerEvent which has all the fields I need for display from the json file.
How can I resolve this because it seems like the contructor or the application itslef is not picking up the data when I make use of a custom class.
This is the code for the EntertainerEvent class:
class EntertainerEvent {
final int eventId;
final int entertainerId;
final int eventTypeId;
final int categoryId;
final String eventName;
final String description;
final String imagePoster;
final String location;
final DateTime startDate;
final DateTime endDate;
final double entreeFee;
const EntertainerEvent({required this.eventId, required this.entertainerId, required this.eventTypeId,
required this.categoryId, required this.eventName, required this.description, required this.imagePoster,
required this.location, required this.startDate, required this.endDate, required this.entreeFee});
factory EntertainerEvent.fromJson(Map<String, dynamic> event) {
return EntertainerEvent(
eventId: event['EventID'],
entertainerId: event['EntertainerID'],
eventTypeId: event['EventTypeID'],
categoryId: event['CategoryID'],
eventName: event['EventName'],
description: event['Description'],
imagePoster: event['ImagePoster'],
location: event['Location'],
startDate: event['StartDate'],
endDate: event['EndDate'],
entreeFee: event['EntryFee'],
);
}
}
Below is the code for fetching data from the api:
Future<List<EntertainerEvent>> fetchEvents() async {
var result = await http.get(Uri.parse(apiUrl));
if (result.statusCode == 200) {
var content = result.body;
var arr = json.decode(content) as List;
return arr.map((eve) => new EntertainerEvent.fromJson(eve)).toList();
} else {
print('Not loaded');
throw Exception('Unable to fetch data from the Rest API');
}
}
late Future<List<EntertainerEvent>> _fetchEvents;
#override
void initState() {
_fetchEvents = fetchEvents();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<EntertainerEvent>>(
future: _fetchEvents,
builder: (BuildContext context, AsyncSnapshot snapshot) {
var childCount = 0;
if (snapshot.connectionState != ConnectionState.done) {
childCount = 1;
} else {
childCount = snapshot.data.length;
}
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
if (snapshot.hasData) {
List<EntertainerEvent> someData = snapshot.data;
print('data here');
//Do some stuff
}
}, childCount: childCount),
);
});
}
I do not know what exactly is it that I am missing because this code works if I use the type dynamic instead of the custom class EntertainerEvent.
Thank you all in advance!
Wrap it with hasData:
if(snapshot.hasData){
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
List<EntertainerEvent> someData = snapshot.data;
print('data here');
//Do some stuff
}, childCount: childCount),
);}
return CircularProgressIndicator();

use StreamBuilder to fetch Firestore document which contains list of map in flutter

I want read data from my firestore collection that has the format below
This is the class below. Don't Know how to use streamBuilder to read the documents. I also want a situation where I can pass a selected order to the next page. Thanks in advance.
class OrderModel2 {
final String uid;
final int subTotal;
final String address;
final String address2;
final String zipCode;
final String phoneNumber;
final dynamic timeCreated;
final String status;
final List<OrdersList> orders;
OrderModel2(
{this.timeCreated,
this.status,
this.subTotal,
this.phoneNumber,
this.zipCode,
this.uid,
this.address2,
this.orders,
this.address});
OrderModel2.fromMap(Map<String, dynamic> data, String uid)
: subTotal = data['subTotal'],
address = data['address'],
zipCode = data['zipCode'],
phoneNumber = data['phoneNumber'],
address2 = data['address2'],
timeCreated = data['timeCreated'],
status = data['status'],
orders = data['orders'],
uid = uid;
}
class OrdersList {
final String productName;
final String selected;
final String image;
final int price;
final String category;
OrdersList(
{this.productName, this.selected, this.image, this.price, this.category});
OrdersList.fromMap(Map<dynamic, dynamic> data)
: productName = data['productName'],
selected = data['selected'],
image = data['image1'],
price = data['newPrice'],
category = data['category'];
}
This is how you would use SteamBuilder to access the documents within the Orders Collection.
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('Orders').snapshots(),
builder: (
BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot,
) {
if (!snapshot.hasData) return new Text('Loading...');
body: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("products").snapshots(),
builder: (context, snapshot) {
return !snapshot.hasData
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot data = snapshot.data.documents[index];
return YourObject(
documentSnapshot: data,
paramI: data.documentID,
paramField: data['field'],
);
},
);
Definitely read this to get a whole picture of what's happening !
https://medium.com/flutterdevs/using-firebase-firestore-in-flutter-b0ea2c62bc7
Also here is a StackOverflow about adding a searchfield also!
Adding SearchField to StreamBuilder Reading from Firestore

Flutter Firestore array with Maps. Do they work?

I can't stream (read) Documents that contain array properties that are Maps in Firestore.
Using Firestore with a document containing an array with a simple String type works as expected. Easy to write (append with FieldValue.arrayUnion(['data1','data2','data3']) and stream back out with code like:
var test2 = List<String>();
for (var item in data['items']) {
print('The item $item');
test2.add(item);
}
test2 can now be used as my items property.
When I try and use a List where item type becomes a Map in Firestore and is just a simple Class containing a few Strings and a date property. I can write these to FireStore but I can't read them back out.
The following code fails: (no errors in the Debug Console but it doesn't run)
var test2 = List<Item>();
data['items'].forEach((item) {
var description = item['description'];
print('The description $description'); // <-- I don't get past this
final x = Item.fromMap(item); // <-- so I can't even attempt this
return test2.add(x);
});
I never get to actually call my Item.fromMap constructor: here is another try:
// fails
final theItems = data['items'].map((x) {
return Item.fromMap(x); // <-- never get here
});
Although the DEBUG CONSOLE doesn't say there is any problem. If I inspect theItems variable (the debugger 'jumps' a couple of lines down to my return) after the failed iteration it looks like this:
MappedListIterable
_f:Closure
_source:List (1 item)
first:Unhandled exception:\ntype '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>'\n#0 new Listing.fromMap.<anonymous closure> isEmpty:false
isNotEmpty:true
iterator:ListIterator
last:Unhandled exception:\ntype '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of
So bad things have happened but I have no idea why!
Has anyone actually written and retrieved Firestore array properties that contain Maps?
Any help in how to proceed would be greatly appreciated!
More: screen shot of the document
Here is the code that reads (streams) the collection
Stream<List<T>> collectionStream<T>({
#required String path,
#required T builder(Map<String, dynamic> data, String documentID),
#required String userId,
Query queryBuilder(Query query),
int sort(T lhs, T rhs),
}) {
Query query = Firestore.instance.collection(path);
if (queryBuilder != null) {
query = queryBuilder(query);
}
final Stream<QuerySnapshot> snapshots = query.snapshots();
return snapshots.map((snapshot) {
//print('document: path $path ${snapshot.documents[0]?.documentID}');
final result = snapshot.documents
.map((snapshot) => builder(snapshot.data, snapshot.documentID))
.where((value) => value != null)
.toList();
if (sort != null) {
result.sort(sort);
}
print('returning from CollectionStream');
return result;
});
}
the .map is where the problem comes in. The builder function resolves to this:
builder: (data, documentId) {
return Listing.fromMap(data, documentId);
},
Which ends up here
factory Listing.fromMap(
Map<String, dynamic> data,
String documentId,
) {
if (data == null) {
return null;
}
final theTime = (data['createdAt'] as Timestamp).toDate();
// see above code where I fail at getting at the items property/field
Here is the Item Class:
class Item {
Item(this.description, this.imageURL, this.thumbnailURL,
{this.status = ItemStatus.active,
this.type = ListingType.free,
this.price = 0,
this.isUpdate = false,
this.createdAt});
final String description;
final String imageURL;
final String thumbnailURL;
final ItemStatus status;
final ListingType type;
final int price;
final bool isUpdate;
DateTime createdAt;
factory Item.fromMap(
Map<String, dynamic> data,
) {
if (data == null) {
return null;
}
final theTime = (data['createdAt'] as Timestamp).toDate();
return Item(
data['description'],
data['imageURL'],
data['thumbnailURL'],
status: _itemStatusFromString(data['status']),
type: data['type'] == 'free' ? ListingType.free : ListingType.flatRate,
createdAt: theTime,
);
}
Map<String, dynamic> toMap() {
// enums are for shit in Dart
final statusString = status.toString().split('.')[1];
final typeString = type.toString().split('.')[1];
return {
'description': description,
'imageURL': imageURL,
'thumbnailURL': thumbnailURL,
'itemStatus': statusString,
'price': price,
'listingType': typeString,
if (!isUpdate) 'createdAt': DateTime.now(),
if (isUpdate) 'updatedAt': DateTime.now(),
};
}
}
The above is never called to read (we crash) ... it is called to write the data.
This is a known issue with Firestore. Here is the starting thread Flutter issues I found
From reading through the issues it seems lots of folks use a serializer package which is where the issue surfaced. Its still active...
Here is my solution NOT using a serializer and just doing it 'by hand'.
I was able simplify the problem and generate an error that was Google-able. Below is a single page which just writes and reads a single Document. The Class A and B are as small as can be.
So the code writes a single document to Firestore the contains two properties. A name and items. The items being the List of Class B that Class A contains. Checkout the Firebase console. The reading just does that.
No StreamController just the Console. The problem is in the fromMap method where we have to convert the array of objects in Firesote to a List of class instances in Dart. This should not be this difficult and at a minimum it should be documented ....
the line
var theRealItems = data['items'].map((i) => B.fromMap(i));
will generate the error. And needs to be replaced with
var theItems = data['items'].map((i) {
var z = Map<String, dynamic>.from(i);
print(z['description']);
return B.fromMap(z);
}).toList();
var theRealItems = List<B>.from(theItems);
Why this is so difficult is still a mystery to me! Anyone improving this code: I'm all ears.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class A {
A(this.name, this.items);
final name;
final List<B> items;
factory A.fromMap(
Map<String, dynamic> data,
String documentId,
) {
if (data == null) {
return null;
}
// we crash here !!!
var theRealItems = data['items'].map((i) => B.fromMap(i));
// uncomment the 6 lines below
// var theItems = data['items'].map((i) {
// var z = Map<String, dynamic>.from(i);
// print(z['description']);
// return B.fromMap(z);
// }).toList();
// var theRealItems = List<B>.from(theItems);
return A(data['name'], theRealItems);
}
Map<String, dynamic> toMap() {
var theItems = items.map((i) => i.toMap()).toList();
return {'name': name, 'items': theItems};
}
}
class B {
B(this.description);
final description;
factory B.fromMap(
Map<String, dynamic> data,
) {
if (data == null) {
return null;
}
return B(data['description']);
}
Map<String, dynamic> toMap() {
return {
'description': description,
};
}
}
class Test extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () => _write(),
child: Text('Write the Doc'),
),
RaisedButton(
onPressed: () => _read(),
child: Text('Read the Doc ... check Debug Console!'),
),
],
),
),
);
}
_write() async {
try {
var b = B('Inside B!');
List<B> theList = List<B>();
theList.add(b);
var a = A('myName', theList);
await Firestore.instance
.collection('test')
.document('testDoc')
.setData(a.toMap());
print('returning from write!');
} catch (e) {
print('Error ${e.toString()}');
}
}
_read() async {
try {
var aFromFs = await Firestore.instance
.collection('test')
.document('testDoc')
.get();
var a = A.fromMap(aFromFs.data, aFromFs.documentID);
print('the A from FireBase $a with name ${a.name} first item ${a.items.first.description}');
print('returning from read!');
} catch (e) {
print('Oh no Error! ${e.toString()}');
}
}
}
In the below code Name refers to the name of your respective document and collection.
Let's say you want to get "imageURL" and "thumbnailUrl" for now and update the values without deleting or changing other fields inside the array.
String imageUrl ;
String thumbnailUrl;
DocumentReference _docReference = FirebaseFirestore.instance.collection(NAME).doc(NAME);
//refering to the specific document
Map<String, dynamic> neededData= allDocsFromTraining.data();
//getting all the keys and values inside the document
List<Map<String, dynamic>> _yourDocument=
(neededData["items"] as List<dynamic>)
.map((m) => Map<String, dynamic>.from(m))
.toList();
//only getting the list of items
for (int index = 0; index < _yourDocument.length; index++) {
Map<String, dynamic> _getMap = _yourDocument[index];
if (_getMap["price"] == 0)
//giving a condition to get the desired value and make changes
{
//setting the variables with the value from the database
setState(() {
imageUrl = _getMap["imageURL"];
thumbnailUrl = _getMap["thumbnailURL"]
});
///Use this if you want to update value of imageURL and thumbnailURL at that specific index of an array
_getMap['imageURL'] = "Your Updated URL";
_getMap['thumbnailURL'] = "Your Updated Url;
break;
} else {}
}
await _docReference.update({"items": _yourDocument});
//this adds the updated value to your database.