How can I get data from various nodes in a Realtime-database in Flutter? - flutter

I'm trying to display data from a real-time database in my widget, such as a picture, a name, or a message, but I'm not sure how to achieve it from several nodes. Thank you in advance for your assistance.
For add data :
List lists = [];
stream to get data :
final dbRef = FirebaseDatabase.instance
.ref()
.child("chatList")
.child("D1NilPUI6PY0jSA1tk0wRzi6FsO2");
Widget to show data :
_widget() {
return StreamBuilder(
stream: dbRef.onValue,
builder: (BuildContext context, AsyncSnapshot snap) {
if (snap.hasData &&
!snap.hasError &&
snap.data!.snapshot.value != null) {
Map data = snap.data.snapshot.value;
List item = [];
data.forEach((index, data) => item.add({"chatList": index, ...data}));
print("DATA : $item");
if (snap.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else {
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: item.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(item[index]['content'].toString()),
);
},
);
}
} else {
return const Center(child: Text("No data"));
}
},
);
}
Table structure Images :
Image 1 :
Image 2 :

This code:
final dbRef = FirebaseDatabase.instance
.ref()
.child("chatList")
.child("D1NilPUI6PY0jSA1tk0wRzi6FsO2");
This refers to a node /chatList/D1NilPUI6PY0jSA1tk0wRzi6FsO2 in your database. Since the screenshot doesn't show any data under that exact path, you will get a snapshot without any value from reading it.
If you want to read all nodes under /chatList, you can use that path in the query, and then loop over all the children of the snapshot.
final dbRef = FirebaseDatabase.instance
.ref()
.child("chatList");
dbRef.onValue.listen((event) => {
event.snapshot.children.forEach((child) {
print(child.key);
})
})
Since you have two levels with dynamic keys under chatList, you'll have to use two nested loops to get to the named properties:
final dbRef = FirebaseDatabase.instance
.ref()
.child("chatList");
dbRef.onValue.listen((event) => {
event.snapshot.children.forEach((child) {
print(child.key);
child.children.forEach((child2) {
print(child2.key);
print(child2.child("lastMessage/content").value);
})
})
})

Related

How to delete documents that contain a certain value in one of the fields. Firestore, Flutter

enter image description here
I have these documents, which contain data about each task I add to the list in my app.
child: StreamBuilder(
stream: _tasks.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];
return GestureDetector(
onLongPress: () => _update(documentSnapshot),
child: ListTile(
)
);
},
);
}
return const Center(
child: CircularProgressIndicator(),
);
},
),
I am using a stream builder to build the list. Each of the tasks have a checkmark and when i click it, It updates the value in firestore inside the IsDone field accordingly. I want to click a button outside the stream builder to delete the checked tasks. How do I loop through all the documents and find all the documents that contain the value true and delete them?
I tried this but im doing doing something wrong and it isnt changing anything:
void _delete() {
var docs = _tasks.doc().snapshots();
docs.forEach((doc){
if(doc.data()==true){
_tasks.doc(doc.id).delete();
}
});
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('You have successfully deleted a product')));
}
var datas = await FirebaseCalls.firebaseFirestore
.collection("collection_id")
.where("status", isEqualTo: true)
.get();
datas.docs.forEach((element) {
FirebaseFirestore.instance
.collection("collection_id")
.doc(element.id)
.delete();
});
or you can do like this as well.
var datas =
await FirebaseCalls.firebaseFirestore.collection("collection_id").get();
datas.docs
.where((element) => element["status"] == true)
.toList()
.forEach((el) {
FirebaseFirestore.instance
.collection("collection_id")
.doc(el.id)
.delete();
});
You can delete by doing this below:
find the particular document as per your query and get its document and collection ID.
FirebaseFirestore.instance
.collection("collection_id")
.doc("doc_id")
.delete();

Future Builder with for loop in flutter

In my application, I have two future builders:
CollectionReference stream = Firestore.instance.collection('users');
List<String> myIDs =[];
List <dynamic> mylist =[];
List<String> myNames =[];
String? userName;
Widget userValues() {
return FutureBuilder(
future: getrecords(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return Text(snapshot.data? [index] ?? "got null");
},
);
}
else {
return CircularProgressIndicator();
}
},
);
}
..................
Future getrecords() async{
final data = await stream.get();
mylist.addAll(data);
mylist.forEach((element) {
final String firstPartString = element.toString().split('{').first;
final String id = firstPartString.split('/').last;
myIDs.add(id.trim());
});
return(myIDs);
}
....................
Widget Names() {
return FutureBuilder(
future: getNames(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return Text(snapshot.data?[index] ?? "got null");
},
);
}
else {
return CircularProgressIndicator();
}
},
);
}
............................
Future getNames() async{
for (var id in myIDs ){
var names = stream.document(id).collection('userName').document('userName');
var document = await names.get();
userName = document['name'];
myNames.add(userName!);
}
return(myNames);
}
The first future (userValues) works fine, and I get the result just fine, but the other one with the for loop is not working properly and is not returning values until I hot reload, then a name will be added to the list, and so on with each hot reload.
What I want to achieve is to keep the loading indicator until the for loop is over, then build the screen.
UPDATE:
If I could manage to make it so that the "Names" futurebuilder awaits for the userValues to complete before starting, then my problem would be solved, but what I realized is that it's taking the initial value of the return from "userValues," which is non, and using it to build.
Future getNames() async{
await Future.delayed(const Duration(seconds: 2));
for (var id in myIDs ){
var names = stream.document(id).collection('userName').document('userName');
var document = await names.get();
userName = document['name'];
myNames.add(userName!);
}
return(myNames);
}
When I added this 2 seconds delay, it worked properly but is there any other way to make it wait for the first future to complete then start the second one?
You can use the await keyword on the future returned from getrecords() to wait for the completion of getrecords() before starting the getNames() function:
Future getNames() async{
await getrecords();
for (var id in myIDs ){
var names = stream.document(id).collection('userName').document('userName');
var document = await names.get();
userName = document['name'];
myNames.add(userName!);
}
return(myNames);
}

firestore doesnt show documents even though they are available

I have following code to add data to firebasefirestore
Future<void> sendMessage({
required String msg,
required String id,
}) async {
var docId = getDocId(id); // returns sth like "AbcDe-FghiJ"
DocumentReference documentReferencer = chat.doc(docId).collection('chatMsg').doc();
Map<String, dynamic> data = <String, dynamic>{
"message": msg,
"sentBy": ownId,
"sentAt": DateFormat('yyyy-MM-dd – kk:mm:ss').format(DateTime.now())
};
await documentReferencer.set(data);
}
I used following code to get the data
StreamBuilder<QuerySnapshot>(
stream: firebaseInstance.collection('Messages').snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError || !snapshot.hasData) {
return const Center(
child: CircularProgressIndicator()
);
} else {
var data = snapshot.data.docs;
return listBuilder(data);
}
}
)
listBuilder(listData) {
return ListView.builder(
shrinkWrap: true,
itemCount: listData.length,
itemBuilder: (BuildContext context, int index) {
return Text(listData[index].id);
}
)
}
However, data show 0 items even though there is a document present.
My question is how can I get the list of documents from Messages?
I was having the same exact problem with subcollections on Firestore and even asked a question here to get some help over it. Though, it seems like the snapshots won't show the documents having a subcollection in them as there is no field inside any of them. So what I did to counter this was to just add anything (just a random variable) and then it was able to find the documents.
This is my current layout:
I've just added another line of code to just add this whenever I'm inserting a new subcollection.
collection
.set({
'dummy': 'data'
})
.then((_) => print('Added'))
.catchError((error) => print('Add failed: $error'));

How can I convert FutureBuilder code to StreamBuilder?

I am trying to get data from Firestore and pass that data to screen using stream. I have done this using FutureBuilder, this solution works as followed, but I need to use StreamBuilder Can anyone help me find the problem?
Future<List<Business>> list(FirebaseFirestore _firesore) async {
CollectionReference _col = _firesore.collection('Buisiness');
var _result = await _col.get();
var _docs = _result.docs;
return List.generate(_docs.length, (index) {
var satir = _docs[index].data();
return Business.fromMap(satir as Map<String, dynamic>);
});
}
This Code works in FutureBuilder but not StreamBuilder
StreamBuilder<List<Business>>(
stream: _firestorelist.list(_firestore), // Error Here
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Business>? data = snapshot.data;
return ListView.builder(
itemCount: data!.length,
itemBuilder: (context, index) {
var result = data[index];
return ListTile(
title: Text(result.nereden),
subtitle: Text(result.nereye),
trailing: Text(result.fiyat),
);
},
);
} else {
return CircularProgressIndicator();
}
},
)```
You can write your data source method as
Stream<List<Business>> list(FirebaseFirestore _firesore) {
CollectionReference _col = _firesore.collection('Buisiness');
final _snap = _col.snapshots();
return _snap.map((event) => event.docs
.map<Business>((e) => Business.fromMap(e.data() as Map<String, dynamic>))
.toList());
}
The current method is a One-time Read method, You can get snapshots from the specific collection.
You can change the method like this and Then use it as stream in streamBuilder:
list(FirebaseFirestore _firesore) async {
CollectionReference _col = _firesore.collection('Buisiness');
var _result = await _col.snapshots();
return _result;
}

Null List after data retrieval from Firestore in Flutter

I am new at Flutter so I am sorry if this problem seems trivial or irrelevant!
I have created another class for getting and setting data, Repository, as I use Cloud Firestore, the data I want for this specific question is stored in a collection, so I get all the documents in the collection as a QuerySnapshot and then add all the documents in a List<DocumentSnapshot>.
Here is the method:
Future<List<DocumentSnapshot>> getCollection(CollectionReference colRef) async {
List<DocumentSnapshot> dummyList = new List();
await colRef.getDocuments().then((value) {
dummyList.addAll(value.documents);
});
return dummyList;
}
Repository:
CollectionReference memoriesColRef =
_firestore
.collection("username")
.document("memories")
.collection("allMem");
List<DocumentSnapshot> documentList = new List();
await getCollection(memoriesColRef).then((value) {
documentList.addAll(value);
});
After all this, I have set up a method in my UI class, to call this Repository, and it works perfectly there bet when I call it in the build function, the global list I have passed to access the data, is not able to add the values in it
UI Class
build(...) {
getMemories().then((value) {
print("value size: " + value.length.toString()); // 1
memoriesListMap.addAll(value); // global variable
});
print("valSize: " + memoriesListMap.length.toString()); // 0
print("val: " + memoriesListMap[0]["title"]); // error line
}
Future<List<Map<String, dynamic>>> getMemories() async{
List<Map<String, dynamic>> dummyListMap = new List();
await MemoryOper().getMemories().then((value) {
print("memVal: " + value[0]["title"]); // appropriate value
dummyListMap.addAll(value);
});
return dummyListMap;
}
ERROR
RangeError (index): Invalid value: Valid value range is empty: 0\
I don't know what's causing this, but please help me out! Thank you
EDIT:
ListView.builder(
itemBuilder: (BuildContext context, int index) {
String title = memoriesListMap[index]["title"]; // error prone line
int remind = memoriesListMap[index]["remind"];
String link = memoriesListMap[index]["link"];
I addition to what nvoigt has said, This article will help you to understand how to implement the Future Builder, in your specific case you can do something like:
build(...){
..
body: getMemories(),
..
}
Widget getMemories() {
return FutureBuilder(
builder: (context, projectSnap) {
if (projectSnap.connectionState == ConnectionState.none &&
projectSnap.hasData == null) {
return Container();
}
return ListView.builder(
itemCount: projectSnap.data.length,
itemBuilder: (context, index) {
ProjectModel project = projectSnap.data[index];
return Column(
children: <Widget>[
// Widget to display the list of project
],
);
},
);
},
future: getCollection(), //this is the important part where you get the data from your Future
);
}
I think you are accessing the element before it gets added since it is async method.
Try something like this,
build(...) {
getMemories().then((value) {
print("value size: " + value.length.toString()); // 1
memoriesListMap.addAll(value); // global variable
print("valSize: " + memoriesListMap.length.toString()); // 0
print("val: " + memoriesListMap[0]["title"]);
});
}
Hope it works!