Flutter firebase_database get children - firebase

Hi I want to deserialise the snapshot from the realtime database to a Company object and add it to a _companies list.
But I keep getting an error...
This is what I have so far:
List<Company> _companies = [];
#override
void initState() {
// TODO: implement initState
super.initState();
getItems().then((list){
print("Now the list is here");
setState(() {
for (int i=0; i < list.length; i++) {
Map<String, dynamic> map = list[i];
Company company = new Company.fromMap(map);
_companies.add(company);
}
});
});
}
static Future<List> getItems( ) async {
Completer<List> completer = new Completer<List>();
FirebaseDatabase.instance
.reference()
.child("Companies")
.once()
.then( (DataSnapshot snapshot) {
List map = snapshot.value; //It fails here
completer.complete(map);
} );
return completer.future;
}
This is my Company class:
class Company {
String key;
String category;
String company_name;
String company_url;
String country;
String description;
String email;
String faq_url;
String instagram_url;
String logo_url_image;
String p_category;
String parent_company_ok;
String timestamp;
Company();
Company.fromSnapshot(DataSnapshot snapshot)
: key = snapshot.key,
category = snapshot.value['category'],
company_name = snapshot.value['company_name'],
company_url = snapshot.value['company_url'],
country = snapshot.value['country'],
description= snapshot.value['description'],
email = snapshot.value['email'],
faq_url = snapshot.value['faq_url'],
instagram_url = snapshot.value['instagram_url'],
logo_url_image = snapshot.value['logo_url_image'],
p_category = snapshot.value['p_category'],
parent_company_ok = snapshot.value['parent_company_ok'],
timestamp = snapshot.value['timestamp'];
}
How ever, it fails in getItems( ) on List map = snapshot.value;. With the exeption: _InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'List<dynamic>'
Also if anyone can provide the code for showing the children in a List widget I would be very happy :)
Here is my data structure in firebase realtime database:

Well as the error message says snapshot.value is not a List but it is a map so the line List map = snapshot.value; will always fail. Since you are reading Companies node tree from your database this will return a map of maps, something like Map<String, Map<dynamic,dynamic>> with all data from respective node. I will provide you a function that parse your Json data in Company object and i will left some comments in source code to try explain you the process. It's simple.
List<Company> _parseData(DataSnapshot dataSnapshot) {
List<Company> companyList =new List();
// here you replace List map = snapshot.value with... dataSnapshot.val()
Map<String, dynamic> mapOfMaps = Map.from( dataSnapshot.val() );
//actually dynamic here is another Map, String are the keys like -LXuQmyuF7E... or LXuSkMdJX...
//of your picture example and dynamic other Map
//in next lines we will interate in all values of your map
//remeber that the values are maps too, values has one map for each company.
//The values are Map<String, dynamic>
//in this case String are keys like category, company_name, country, etc...
//and dynamics data like string, int, float, etc
//parsing and adding each Company object to list
mapOfMaps.values.forEach( (value) {
companyList.add(
//here you'll not use fromSnapshot to parse data,
//i thing you got why we're not using fromSnapshot
Company.fromJson( Map.from(value) )
);
});
return companyList;
}
Note you will use Company.fromSnapshot(snapshot) when you read a specific company from
your db, something like ...
// just to illustrate
var snapshot = await FirebaseDatabase.instance
.reference()
.child("Companies")
.child(companyId).once();
Company.fromSnapshot( snapshot );
because in this case the snapshot.value is a single map.
Well another thing is, take a look at you initState method in your statefull widget, i don't know if is a good
approach call setState inside initState method even after a Future execution. This is just an advise.

snapshot.value returns a Map and you are using a List to store it. Maybe the easiest solution here is to use the var type to declare your map variable:
var map = snapshot.value;
but it's a good practice to set a type whenever you can to your variables. So, in this case the best option is to set it like this:
Map<dynamic, dynamic> map = snapshot.value;
Check dart's documentation to improve your skills in useing maps and list and how they work Lists docs Maps docs

Related

Filtered firebase-calls in one method, filtered by named arguments don't work -> wrong type of variable?

I tried to use the same method with a named argument to get filtered/unfiltered data from firebase.
If I declare the variable polylinesRef as final and I use only one firebase reference
without the condition/differ of the named argument "onlyOwnPolys" I get the expected correct result from firebase in "polylinesRef".
If I declare "polylinesRef" before the condition as var and I try to use it in the condition, I don't get any result.
How do I have to declare "polylinesRef" correctly? What is my mistake?
void _fbListenPolylines({bool onlyOwnPolys}) async {
final currentUserUuid =
await PrefHelper.getLoginResponse().then((r) => r.uuid);
final firebase = (await FirebaseHelper.fbDb);
var polylinesRef;
if (onlyOwnPolys == true) {
polylinesRef = firebase
.reference()
.child('new_polys/$orderUuid')
.orderByChild("userUuid")
.startAt('$currentUserUuid')
.endAt('$currentUserUuid');
} else {
polylinesRef = firebase.reference().child('new_polys/$orderUuid');
}
_messagesSubscription?.cancel();
_messagesSubscription = polylinesRef.onValue
.debounceTime(Duration(seconds: 1))
.listen((Event event) {
final Map<dynamic, dynamic> map = event.snapshot.value;
final orderSegments = map?.entries?.toList();enter code here
You can use the conditional operator(? :) when declaring
final polyLineRefs = onlyOwnPolys
? firebase
.reference()
.child('new_polys/$orderUuid')
.orderByChild("userUuid")
.startAt('$currentUserUuid')
.endAt('$currentUserUuid')
: firebase.reference().child('new_polys/$orderUuid');
Also, for future reference, you do not need to do boolean operation on boolean values.
✅if (myBoolean)
❌if (myBoolean == true)

Flutter/Firestore- Retrieving product information issue

I am trying to load all of my product data from firestore. I have a data schema:
class SingleProduct with ChangeNotifier {
static const EVENT = "event";
static const IMGURL = "imgUrl";
static const NAME = "name";
static const PRICE = "price";
//Private Variables
String _event;
String _imgUrl;
String _name;
double _price;
// getters
String get event => _event;
String get imgUrl => _imgUrl;
String get name => _name;
double get price => _price;
SingleProduct.fromSnapshot(DocumentSnapshot snapshot) {
_event = snapshot.data()[EVENT];
_imgUrl = snapshot.data()[IMGURL];
_name = snapshot.data()[NAME];
_price = snapshot.data()[PRICE];}
}
I have then created a class to map all the data received to a list of products:
class ProductServices {
FirebaseFirestore _firestore = FirebaseFirestore.instance;
String collection = 'products';
Future<List<SingleProduct>> getAllProducts() async =>
_firestore.collection(collection).get().then((snap) {
print(snap.docs.length); // returns 11 products as expected
List<SingleProduct> allProducts = [];
snap.docs.map((snapshot) =>
allProducts.add(SingleProduct.fromSnapshot(snapshot)));
print(allProducts.length); //returns 0 so I think my map isn't working
return allProducts;
});
}
The Firestore query returns 11 query snapshots as expected but I then try and add them to a list using my product schema but the the results show the list has 0 elements. Any suggestions how to map the results of my fire base query to a list?
Use forEach():
snap.docs.forEach((snapshot) => allProducts.add(SingleProduct.fromSnapshot(snapshot)));
Explanation time: [tl;dr Your lambda wasn't executed at all]
map() is a lazy function intended for transforming elements in a list, which works only if the result is going to be used.
In your code, the result of map() is nowhere used later (eg. assigned to a variable), which is why the lambda within it is not called (why would it transform if the transformation is not used ahead?)
Also, it's not apt for your use-case.
To demonstrate its laziness, try running this code in DartPad:
void main() {
List<String> list = ["a", "b", "c"];
List<String> anotherList = [];
var mappingOutput = list.map((element) { anotherList.add(element); return element + "X"; }).toList();
print(list);
print(mappingOutput);
print(anotherList);
}
Notice that the result of map() is to be given back to a variable mandatorily, which pushes the laziness aside and executes it.
anotherList will be filled.

how to return a stream list from a for loop in flutter

The code and comments kind of explains it better than I can articulate it.
basically I want to return a Stream of something but only based on certain parameters. Those parameters are coming from an array.
here is an example.
say we have an array with values ["1", "2", "3"]
and in the database I have a docids of ["1", "2","3","4"]
I want a stream that will return everything but that four, or to better articulate it. I want a stream list that will return only the items that have the docid of the array with those values specified aka [1,2,3]
what I did below was loop through the example array so the first item "c" will be "1".
it will take this "1" and use a where to see if a docid matches this "1". I need to store this somehow and then return it once it is "fullly" populated. or populated at all since it is a stream. The example array of [1,2,3] could change in the future to maybe [1,2,3,4] so when that happens I would like the data to be pulled from the database.
class UserData {
String uid;
String firstName;
int rating;
List<String> classes; //need to be able to access this
UserData.fromMap(Map<String, dynamic> data) {
firstName = data['firstname'] ?? "";
rating = data['rating'] ?? "";
classes = data['classes'].cast<String>() ?? "";
}
Stream<List<ClassData>> getTheUserClasses = (() async* {
List<ClassData> d = List<ClassData>();
for (String c in classes) { //no longer able to access classes
// this will store the data of the matched value
var _matchData = Firestore.instance
.collection("Classes")
.where('classid', isEqualTo: c)
.snapshots()
.map((event) => event.documents
.map((e) => ClassData.fromUserMap(e.data, e.documentID)));
// and when you have it, append
d.add(_matchData); //error here from type differences
}
// after the loop you can test your response then yield
d.forEach((item) => print(item));
// return the data which is required
yield d;
})();
UserData({this.firstName, this.rating, this.classes});
}
Here is a way I have already done this. The problem is that it won't refresh the widget tree when data is updated.
Future<void> getTheUserClasses() async {
List<ClassData> _classList = List<ClassData>();
for (String c in user.classes) {
DocumentSnapshot classsnapshot =
await Firestore.instance.collection("Classes").document(c).get();
final data =
ClassData.fromUserMap(classsnapshot.data, classsnapshot.documentID);
if (data != null) {
_classList.add(data);
}
}
notifyListeners();
classes = _classList;
}

How to get documents from different collections in firebase and add them to a single List to return as Stream?

I am trying to create an Attendance App, so I want to get the courses in which the students are registered. The Student's class looks something like this:
class Student {
final String name;
final String email;
final String number;
final String regNo;
List<CourseIDAndInstructorID> courses;
Student({this.name, this.email, this.number, this.regNo});
}
The List named courses contains the document-ID of the instructor whose course it is, and the document-ID of the course document.(As one student would obviously be taking classes from different instructors)
Now using these two fields, I want to get the documents of the courses, create an Object of the custom class Course and then add this object to a List, that would be returned so that it can be displayed on the GUI.
But I am getting this Exception, whereas I clearly have data in the object.
The image of the Exception Message can be seen here
final CollectionReference _instructorCollection =
Firestore.instance.collection('instructors');
final CollectionReference _studentCollection =
Firestore.instance.collection('students');
Stream<List<Course>> _getStudentCourses() async* {
List<Course> courseList;
DocumentSnapshot stdDoc = await _studentCollection.document(uid).get();
Student _student = new Student.fromSnapshot(stdDoc);
if (_student.courses.length == 0) {
return; //TODO add appropriate return type
} else {
int len = _student.courses.length;
while (len != 0) {
len--;
DocumentSnapshot snapshot = await _instructorCollection
.document(_student.courses[len].instructorUID)
.collection('courses')
.document(_student.courses[len].courseID)
.get();
print(snapshot.data);
Course course = new Course.fromSnapshot(snapshot);
courseList.add(course);
yield courseList;
}
}
}
Can someone tell me, what I'm doing wrong or how I can do this?
If something is missing from the context of the question, kindly tell me so that I can add that.
You may need to create the List object instead of just a pointer.
List<Course> courseList = new List();
source

How to save a List of type map into firestore, and then read/write to the created maps?

I am trying to create a small app were the user can create flash cards. At first, I have them create the title, then all titles will be listed, once the user clicks on that title they'll be taken to a different screen where they can create the question and answer.
My Issue is that I created a List of type Map but can't figure out how to add and save to the lists that are created in the maps.
Model
class Cards {
//final List<String> question;
//final List<String> answer;
//final String title;
final String uid;
final List<Map<String, dynamic>> classes;
Cards({ this.uid, this.classes });
}
Home
This is where it starts, the elList.add only runs once when the user creates a title.
Cards indexData = snapshot.data;
List<Map<String, dynamic>> elList = [];
for (var i = 0; i < indexData.classes.length; i++) {
elList.add(indexData.classes[i]);
}
elList.add({
"title": title,
"question": [],
"answer": []
});
DatabaseService(uid: userId.uid).settingUserData(elList);
// Send the values to another screen where the user creates Q&A
MaterialPageRoute(
builder: (context) => ViewIndex(
questions: elList[index]["question"],
answers: elList[index]["answer"]
),
),
Service
This is where it gets saved to the db. My issue here is trying to figure out how to add on to the lists inside the map because this only adds to the list classes, which indexes all the maps. This runs when the title is created. Trying to figure out how to add to lists inside of map and save them? I tried something like: "classes[question]" and "classes.question", but none work.
Future settingUserData(List<Map<String, dynamic>> listCard) async {
return await _collref.document(uid).setData({
"classes": listCard
});
}
ViewIndex
This is where I receive them from the home file as params via the widget. This is where the user creates the questions and answers. Trying to add to them gives me an error, saying they are fixed-length. So here is where I also need to save them to the database, but as seen in my service file above, I don't know how to save these separately without having to create a whole new index to the list classes, which isn't what I want.
widget.answers.add("foo")
widget.questions.add("foo")
DatabaseService(uid: userId.uid).settingUserData();
I think the better solution for you is u define another class for your Classes as a model to become
class Cards {
//final List<String> question;
//final List<String> answer;
//final String title;
final String uid;
final List<YourClass> classes;
Cards(this.uid, this.classes);
}
class YourClass {
final Map<String, dynamic> yourObject;
YourClass(this.yourObject);
}
Then, initialized yourClass with the object you wanted, and assign it to your Cards.
List<YourClass> classes = List<YourClass>();
Map<String, dynamic> myItem = jsonDecode('{"hello": "dart","world": {"json": "blabla"}}');
// create your classItem
YourClass class1 = YourClass(myItem);
// add your classItem to a your classes list
classes.add(class1);
// add your classes to your card
Cards card = Cards('myUid', classes);
print(jsonEncode(card.classes[0].yourObject['hello']));
play around here: example
I can't believe I didn't notice this... But all I needed to do was to add the passed arguments from home to view index AND then move them to an array and loop through them
ViewIndex
List<dynamic> userAnswer = [];
List<dynamic> userQuestion = [];
for (var i = 0; i < widget.questions.length; i++) {
userQuestion.add(widget.questions[i]);
}
for (var i = 0; i < widget.answers.length; i++) {
userAnswer.add(widget.answers[i]);
}
DatabaseService(uid: userId.uid)
.updattingUserData(widget.index, userQuestion, userAnswer);
Service
Here I then used dot notation to read and write to that specific list in the maps. I also needed to pass the index to know, well, to know which one I was writing to. I made a separate function for this. The reason it was a fixed length was that I didn't have it in a list.
Future updattingUserData(int index, List<dynamic> question, List<dynamic> answer) async {
return await _collref.document(uid).updateData({
"classes.$index.questions": FieldValue.arrayUnion(question),
"classes.$index.answers": FieldValue.arrayUnion(answer)
});
}
Everything else is exactly the same as I have in my First post.

Resources