Display sub-collection in flutter Firebase - flutter

I want to display the data of a sub-collection named "Profile". I get it that we need to query it differently, and I tried it, but it is not working out for me. First, I displayed the information from the documents of the mother collection "mentors", using StreamBuilder. Then passed it's data to a Widget I created. Then on the Widget I created, I performed another streamBuilder query for the subcollection of each document of the Mother Collection "mentors".
This is the code I used to display the documents on "mentors" collection, and is working fine.
final mentors = Expanded(
child: Container(
height: 250,
margin: const EdgeInsets.only(left: 20, right: 20),
child: StreamBuilder<QuerySnapshot>(
stream: db_mentors,
builder: (
BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot,
) {
if (snapshot.hasError) {
Fluttertoast.showToast(msg: "An Error Occured");
}
if (snapshot.connectionState == ConnectionState.waiting) {
Fluttertoast.showToast(msg: "Loading");
}
final data = snapshot.requireData;
return ListView.builder(
itemCount: data.size,
itemBuilder: ((context, index) {
return mentorsWidget(
"${data.docs[index]["uid"]}",
"${data.docs[index]['name']}",
"${data.docs[index]['specialty']}",
);
}),
);
}),
),
);
This here is the code I used to display the data from the subcollection of each document named "Profile". Which is also the widget I created.
Widget mentorsWidget(String uid, String name, String specialty) {
return Container(
margin: const EdgeInsets.all(5),
width: size.width,
decoration: const BoxDecoration(
color: Color.fromARGB(255, 3, 42, 134),
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
bottomRight: Radius.circular(20))),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("mentors")
.doc(uid)
.collection("Profile")
.snapshots(),
builder: (
BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot,
) {
if (!snapshot.hasData) {
return SizedBox(
width: 80,
child: Image.asset("assets/Navigatu-icon.ico"),
);
} else {
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: ((context, index) {
String url = snapshot.data!.docs[index]['downloadURL'];
return SizedBox(
width: 80,
child: Image.network(url),
);
}),
);
}
}),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(top: 10, left: 5),
child: Text(
name,
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontFamily: 'Roboto',
fontWeight: FontWeight.w500),
),
),
Container(
margin: const EdgeInsets.only(top: 15, bottom: 15, left: 5),
child: Text(
specialty,
style: const TextStyle(
color: Colors.white,
fontFamily: 'Roboto',
fontWeight: FontWeight.w400,
fontSize: 12,
),
),
),
],
)
],
),
);
}
Here is the Collection Tree in my firebase:
Firebase Collection Tree
Here is the display I want to achieve. The boat picture here supposedly must be a Image.network, with the url that is in the sub-collection, named "Profile".
Mentor Field
As you can see in the code, I performed first the "final mentors", then performing streambuilder inside of it. So that I can get the datas of each document from the mother collection. Now I passed those data to the "mentorwidget" to display them in a proper way, but then I wanna use a Image.network, containing the data inside the sub-collection of each document in the mother collection. That's why I performed another streambuilder inside the mentorwidget to display the picture, or get the data of the sub-collection which is the url of the said picture.

If the data doesn't get frequently updated or if you don't need to display the constant changes of it's value to the users then use FutureBuilder instead of StreamBuilder to query the value you want only once.
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<QuerySnapshot>(
future: FirebaseFirestore.instance
.collection('mentors')
.doc('b23lt...[your desired document ID]')
.collection('Profile')
.get(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return ListView.builder(
itemCount: , // lenght of snapshot data,
itemBuilder: (context, index) {
//Here you can retrieve the data of each document
},
);
}
return const Center(child: CircularProgressIndicator());
},
),
);
}
}

Update: I found the answer! thanks to Slender's answer, I managed to get the answer, here is the code.
FutureBuilder<QuerySnapshot>(
future: FirebaseFirestore.instance
.collection("mentors")
.doc(uid)
.collection("profile")
.get(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data!.docs.isEmpty) {
return SizedBox(
width: 80,
child: Image.asset("assets/Navigatu-icon.ico"),
);
} else if (snapshot.hasData) {
// print(snapshot);
return SizedBox(
width: 80,
child: Image.network(
"${snapshot.data!.docs[0]['downloadURL']}"),
);
}
}
// print(snapshot.data!.docs[0]['downloadURL']);
return const SizedBox(
width: 80,
child: Center(
child: CircularProgressIndicator(),
),
);
},
),

Related

Why StreamBuilder always has no data before hot reload?

I use firestore and streambuilder to read data in a list, when i run the application for the first time i get a message "Unexpected null value" and I realized that "snapshot.hasData" is always false and snapshot.ConnectionState.waiting is always true. But when i restart application with hot reload i can retrieve data.
This is my stream:
Stream<QuerySnapshot> _branchStream = FirebaseFirestore.instance.collection('Companies').doc(company_id).collection("Branch Offices").snapshots();
This is my StreamBuilder
StreamBuilder<QuerySnapshot>(
stream: _branchStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
/* if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}*/
return ListView(
children: snapshot.data!.docs
.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return Padding(
padding: const EdgeInsets.all(22.0),
child: Card(
elevation: 8,
shadowColor: Colors.blueGrey,
shape: cardShape,
child: Row(
children: [
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(22.0),
child: CircleAvatar(
radius: 50,
backgroundImage:
NetworkImage(data['branch_image'],scale: 60),
),
)),
Expanded(
flex: 4,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(22.0),
child: Text(data['branch_name'], style: textBlackTitle, textAlign: TextAlign.center,),
),
Padding(
padding: const EdgeInsets.all(22.0),
child: Text("Ubicación: "+data['branch_address'], style: textGraySubTitle, textAlign: TextAlign.center,),
),
],
)),
Expanded(
flex: 2,
child: IconButton(
// focusColor: Color(color1),
// color: Color(color1),
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => Home(branch_id : data['branch_id'], company_id : company_id, branch_name : data['branch_name'], branch_image : data['branch_image'])));
}, icon: Image.asset("assets/enter.png", fit: BoxFit.contain, height: 100,)))
],
),
),
);
})
.toList()
.cast(),
);
},
)
This is data that I want to get
This is what I get at the first time
This is what I get after hot reload (That I should have from the beginning).
Because your data is null at the beginning, it takes some time to load the data.
You actually already included a check, but commented it out again. Undo it and try again.
/* if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}*/
It takes some time to load snapshot data. For better UX return specific widgets for each state of the snapshot.
Make sure you're using StreamBuilder inside StatefulWidget.
StreamBuilder<QuerySnapshot>(
stream: _branchStream,
builder: (BuildContext context, snapshot) {
if (snapshot.hasError) {
return //error widget
} else {
switch (snapshot.connectionState) {
case ConnectionState.none:
return //some widget
case ConnectionState.waiting:
return CircularProgressIndicator(),
case ConnectionState.active:
return ListView()
case ConnectionState.done:
return //some widget
}
}
);

flutter: how can I retrieve current user data from firebase firestore

am trying to retrieve current user data but it showing all the users data from firestore in app
here is my code
Widget build(BuildContext context){
CollectionReference users = FirebaseFirestore.instance.collection('users');
return FutureBuilder<DocumentSnapshot>(
future: users.doc(documentId).get(),
builder: ((context, snapshot){
if(snapshot.connectionState == ConnectionState.done){
Map<String, dynamic> data =
snapshot.data!.data() as Map<String, dynamic>;
return Container(
child: Column(
children: [
Text('Email : '"${data['Email name'].toString()}",
style: const TextStyle(fontWeight: FontWeight.bold,wordSpacing: 10),),
const SizedBox(
width: 10,
),
Text('Contact : '"${data['age'].toString()}",
style: const TextStyle(fontWeight: FontWeight.bold,wordSpacing: 10),),
const SizedBox(
width: 10,
),
Text('Name : '"${data['firstname'].toString()}",
style: const TextStyle(fontWeight: FontWeight.bold,wordSpacing: 10),),
const SizedBox(
width: 10,
),
Text('Last Name : '"${data['lastname'].toString()}",
style: const TextStyle(fontWeight: FontWeight.bold,wordSpacing: 10),),
const SizedBox(
width: 10,
),
],
),
);
}
return const Text('Loading');
}),
here is documentId code
List<String> docIDs = [];
Future getDocId() async{
await FirebaseFirestore.instance.collection('users').get().then(
(snapShot) => snapShot.docs.forEach(
(documant){
print(documant.reference);
docIDs.add(documant.reference.id);
}));
}
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// ignore: prefer_const_constructors
Text('SignUp as ${user!.email!}',style: TextStyle(
fontSize: 20,
),),
Expanded(child: FutureBuilder(
future: getDocId(),
builder: (context, snapshot){
return ListView.builder(
itemCount: docIDs.length,
itemBuilder: (context, index){
return ListTile(
title: GetuserData(documentId: docIDs[index]),
);
},
);
},
))
when ever user post the form it submitted successfully and generate a post like summited form history but it showing to others users to but i wont it for only current user how to do that please any one can help me
You can use firestore query to return the current user using where()
on getDocId():
await FirebaseFirestore.instance.collection('users').get()
change to
await FirebaseFirestore.instance.collection('users')
.where('Email name', isEqualTo: 'myEmail#mail.test').get()
you can replace the email with your or consider using parameter to pass user email dynamically.

Flutter - Nested Streambuilder and FutureBuilder

I have categories collection with category name and id fields. In foods collection, the category is a reference field. I need to display like this:click to see expected output
where title is coming from categories collection and foods are coming from foods collection.
I tried using nested streambuilder: streambuilder 1: fetch categories in a listview streambuilder 2: fetch foods in a list. Inside streambuilder 2, i have used a futurebuilder to decode the category data. If category name in food and category name from streambuilder 1 is same, the food will be displayed under that category.+
class RestaurantDetails extends StatefulWidget {
final String id;
RestaurantDetails({required this.id, super.key});
#override
State<RestaurantDetails> createState() => _RestaurantDetailsState();
}
class _RestaurantDetailsState extends State<RestaurantDetails> {
List<FoodCategory> categories = [];
List<Food> foods = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: StreamBuilder(
stream: getCategories(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
categories = snapshot.data!.docs
.map((item) => FoodCategory.fromMap(item))
.toList();
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: categories.length,
itemBuilder: ((context, cateindex) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 10),
child: Container(
height: 30,
width: MediaQuery.of(context).size.width * 1,
color: Colors.white,
child: Text(
categories[cateindex].name.toString(),
style: Theme.of(context)
.textTheme
.headline4!
.copyWith(fontSize: 20.0),
),
),
),
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('foods')
.doc(widget.id)
.collection('all')
.snapshots(),
builder: (context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
foods = snapshot.data!.docs
.map((item) => Food.fromMap(item))
.toList();
return ListView.builder(
itemCount: foods.length,
physics:
const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: ((context, foodindex) {
var catepath = foods[foodindex].cid!.path;
String cateDocumentName = catepath
.substring(11, catepath.length);
return Column(
children: [
FutureBuilder(
future: FirebaseFirestore.instance
.collection('categories')
.doc(cateDocumentName)
.get(),
builder: ((context,
AsyncSnapshot<
DocumentSnapshot>
snapshot) {
if (snapshot.hasData) {
Map<String, dynamic> data =
snapshot.data!.data()
as Map<String,
dynamic>;
if (data['name'] ==
categories[cateindex]
.name) {
return Padding(
padding: const EdgeInsets
.symmetric(
vertical: 10,
horizontal: 10),
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius
.circular(
20),
color:
Colors.white),
height: 100,
width: MediaQuery.of(
context)
.size
.width *
1,
child: Row(
children: [
Image.network(
foods[foodindex]
.cover
.toString(),
height: 100,
width: 100,
errorBuilder:
((context,
error,
stackTrace) {
return Image
.asset(
'assets/images/food1.jpg',
height: 100,
width: 100,
);
}),
),
UIHelper
.horizontalSpaceMedium(),
Column(
crossAxisAlignment:
CrossAxisAlignment
.start,
children: [
UIHelper
.verticalSpaceSmall(),
Text(
foods[foodindex]
.name
.toString(),
style: Theme.of(
context)
.textTheme
.bodyText1,
),
UIHelper
.verticalSpaceSmall(),
Text(
'₹${foods[foodindex].price}',
style: Theme.of(
context)
.textTheme
.bodyText1!
.copyWith(
fontSize:
14.0),
),
UIHelper
.verticalSpaceMedium(),
],
)
],
)),
);
} else {
return const SizedBox();
}
} else {
return const CircularProgressIndicator
.adaptive();
}
}))
],
);
}));
} else {
return const CircularProgressIndicator
.adaptive();
}
},
)
],
);
}));
} else {
return const CircularProgressIndicator.adaptive();
}
}),
),
);
}
getCategories() {
return FirebaseFirestore.instance
.collection('categories')
.where('uid', isEqualTo: widget.id)
.snapshots();
}
}
categories data
click to see categories
food data
click to see food data
I get the output.see my output here but when data is large (i.e large number of foods inside a category) the app hangs. Is there anyother way we can achieve this? the data should load seamlessly regardless of data size.

Can't retrieve data from nested object firestore streambuilder listview

I'm new using firestore, so im still trying to understand it.
i had Closets on the inside i had Clothes. i want to retrieve Clothes data and show it with listview.
the problem is i failed to retrieve the data and show it into the app
this is my code for the streambuilder
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection("clothes").snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Failed to load data!");
}
if (snapshot.connectionState ==
ConnectionState.waiting) {
return ActivityServices.loadings();
}
return new ListView(
children: snapshot.data.docs
.map((DocumentSnapshot doc) {
Clothes clothes;
clothes = new Clothes(
doc.data()['clothesId'],
doc.data()['clothesName'],
doc.data()['clothesDesc'],
doc.data()['clothesImage'],
doc.data()['clothesCloset'],
doc.data()['clothesAge'],
doc.data()['clothesTag'],
doc.data()['clothesStatus'],
doc.data()['clothesLaundry'],
doc.data()['createdAt'],
doc.data()['updatedAt'],
);
print(doc.data()['clothesName']);
return CardClothesLemari(clothes: clothes);
}).toList(),
);
},
),
and this is my CardClothesLemari
final Clothes clothes;
CardClothesLemari({this.clothes, this.doc});
#override
_CardClothesLemariState createState() => _CardClothesLemariState();
}
class _CardClothesLemariState extends State<CardClothesLemari> {
#override
Widget build(BuildContext context) {
Clothes cloth = widget.clothes;
final Size size = MediaQuery.of(context).size;
if (clothes == null) {
return Container();
} else {
return Padding(
padding:
EdgeInsets.only(top: 5.0, bottom: 5.0, left: 5.0, right: 5.0),
child: InkWell(
onTap: () {
Navigator.pushNamed(context, DetailClothes.routeName,
arguments: cloth);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14.0),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
spreadRadius: 3.0,
blurRadius: 5.0)
],
color: Color(0xffA77665),
),
child: Column(children: [
Padding(
padding: EdgeInsets.only(top: size.height * 0.04),
),
Hero(
tag: 'assets/images/dummy.jpg',
child: CircleAvatar(
radius: 55,
backgroundImage: AssetImage("assets/images/dummy.jpg"),
),
),
SizedBox(height: 7.0),
Text(
//show clothes name
cloth.clothes,
style: TextStyle(
fontSize: 14,
fontFamily: GoogleFonts.openSans().fontFamily,
fontWeight: FontWeight.w700,
color: Color(0xffF0E8E1)),
textAlign: TextAlign.center,
),
Padding(
padding: EdgeInsets.only(top: 8),
child:
Container(color: Color(0xFFEBEBEB), height: 2.9657),
),
]))));
}
}
}
this is the screenshot of my firestore
Add listview inside the ConnectionState.done like below code.
if (snapshot.connectionState == ConnectionState.done) {
return new ListView(
children: snapshot.data.docs
.map((DocumentSnapshot doc) {
Clothes clothes;
clothes = new Clothes(
doc.data()['clothesId'],
doc.data()['clothesName'],
doc.data()['clothesDesc'],..........<Rest of the code>......
}
As per your database structure you're entering wrong query. Kindly take a look below code
StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance
.collection('closet')
.doc('your_document_id')
.collection('clothes')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text('Loading...');
} else {
return ListView.builder(
itemCount: snapshot.data.docs.length,
shrinkWrap: true,
itemBuilder: (context, int index) {
QueryDocumentSnapshot<Map<String, dynamic>> data = snapshot.data.docs[index];
return Text(data.data()['clothesName']);
},
);
}
});

How to get doc of a collection with specifc condition from cloud firestore

I am using StreamBuilder to get only those docs from the articles collection which satisfies the condition. I used where clause but it is getting all docs and sorting them.
(Note: I want only those docs which satisfy condition in where not all.)
updated code detail: Used another streambuilder to get the ids from the following field in a doc of users collection.
Here how am I doing it:
StreamBuilder<DocumentSnapshot>(
stream: FirebaseFirestore.instance
.collection('users')
.doc(profileScreenController.currentUser.uid)
.snapshots(),
builder: (context,
AsyncSnapshot<DocumentSnapshot> futureSnapShot) {
if (!futureSnapShot.hasData &&
futureSnapShot.data == null) {
return SizedBox();
}
DocumentSnapshot documentSnapshot =
futureSnapShot.data;
return Container(
height: screenHeight * 0.32,
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('articles')
.where('userId',
isEqualTo: documentSnapshot['following'])
.snapshots(),
builder: (context,
AsyncSnapshot<QuerySnapshot> snapshot) {
print(documentSnapshot['following']);
if (!snapshot.hasData &&
snapshot.data == null) {
return Center(
child: Lottie.asset('assets/loading.json'),
);
}
if (snapshot.data.docs.length == 0) {
return Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Lottie.asset('assets/no articles.json',
height: 140),
SizedBox(
height: 10,
),
Text(
'No body yet',
style: TextStyle(
fontSize: 20,
color: Theme.of(context)
.textTheme
.headline1
.color,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
return ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: snapshot.data.docs.length,
padding: EdgeInsets.only(left: 15),
itemBuilder: (context, index) {
selectedIndex = index;
DocumentSnapshot todaysArticles =
snapshot.data.docs[index];
return Container(
width: screenWidth * 0.8,
margin: EdgeInsets.only(right: 6),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(30),
),
child: Text(todaysArticles.data()
['title'],
),
);
});
},
),
);
}),
Result: getting doc of both users but not only where username is Guman as my where said.
My database with 2 docs in articles collection:
I believe the issue here is that the value of profileScreenController.followerName is null when creating the stream.
When sending a query like where('db_field', isEqualTo: null) the database just returns all the values from the previous query (which in your case is the whole collection).
Please check & debug if the value of profileScreenController.followerName is not null when you first open the stream.
Maybe you forgot to use a setState somewhere?