Flutter: get a document from Firestore inside a widget that returns a Widget - flutter

In my build I have this StreamBuilder which will get the data of the posts
and map it into a method that will return a widget to build the posts
StreamBuilder<List<Post>>(
stream: readPosts(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.data == null) {
print(snapshot.error.toString());
return Text(snapshot.error.toString());
} else {
final posts = snapshot.data;
print('from streambuilder');
return Column(
children: posts!.map(buildPost).toList(),
);
}
} else {
return Center(
child: CircularProgressIndicator(),
);
}
})
this is the readPosts function that provides the stream of the posts
Stream<List<Post>> readPosts() {
return FirebaseFirestore.instance
.collection('posts')
.where('availableFor', whereIn: ['All', 'Business Adminstration'])
.snapshots()
.map((snapshot) {
// print(snapshot);
return snapshot.docs.map((doc) {
// print(doc.data());
return Post.fromJson(doc.data());
}).toList();
});
}
and then the the list of posts are mapped into the buildPost function which will return the post widget
Widget buildPost(Post post) {
final organizations = getUserData(post.owner) //I want this final property to get an
Organization value as a return type
// however it is returning a Future<Organizations>
value
//is there any way I can use to convert
it to an Organizations type?
//and I want to keep this function as a
widget so that the stream builder
//does not give me an error
return Container(
padding: EdgeInsets.all(10),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0))),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8.0),
topRight: Radius.circular(8.0),
),
child: Image.network(post.imageUrl,
loadingBuilder: ((context, child, loadingProgress) {
return loadingProgress == null
? child
: LinearProgressIndicator();
}), height: 200, fit: BoxFit.fill
),
),
ListTile(
isThreeLine: true,
subtitle: Text(
'valid until: ${post.validUntil} for ${post.availableFor}',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade400,
fontWeight: FontWeight.bold),
),
leading: ClipOval(
child: Container(
height: 30,
width: 30,
child: Image.network(organizations!.imageUrl,
loadingBuilder: ((context, child, loadingProgress) {
return loadingProgress == null
? child
: LinearProgressIndicator();
}), height: 200, fit: BoxFit.fill
),
),
),
title: Text(
post.description,
style: TextStyle(fontSize: 14),
),
trailing: Text(organizations!.id),
)
],
),
),
);
}
this is the getUserData function
Future<Organizations> getUserData(organizationId) async {
var organizations;
await FirebaseFirestore.instance
.collection('organizations')
.where('id', isEqualTo: organizationId)
.get()
.then((event) {
if (event.docs.isNotEmpty) {
Map<String, dynamic> documentData =
event.docs.single.data(); //if it is a single document
print(documentData.toString());
organizations = Organizations.fromJson(documentData);
}
}).catchError((e) => print("error fetching data: $e"));
return organizations;
}
is there a way to use the organizations data in build post method?

Related

Display sub-collection in flutter Firebase

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(),
),
);
},
),

Flutter: Unable to refetch image after updated image in Firebase

Previously in my log in page, I prompted user to upload a profile image, then the user is able to edit their profile picture by re-upload another new profile image. However, once the user selected the new image, the link is able to update on the Firebase side but the new link image requires hot reload in order to reflect the new image on the page. Anyone knows a better solution to it? My pick image function:
Future pickImage(ImageSource source) async {
try {
final image = await ImagePicker().pickImage(source: source);
if (image == null) return;
setState(() => _imageFile = File(image.path));
String fileName = Path.basename(_imageFile!.path);
Reference firebaseStorageRef =
FirebaseStorage.instance.ref().child('$fileName');
await firebaseStorageRef.putFile(File(image.path));
setState(() async {
imageUrl = await firebaseStorageRef.getDownloadURL();
await FirebaseFirestore.instance
.collection('MerchantData')
.doc(widget.currentUser?.uid)
.update({"shopLogo": imageUrl}).then((value) => {
showFlash(
context: context,
duration: const Duration(seconds: 2),
builder: (context, controller) {
return Flash.bar(
controller: controller,
backgroundColor: Colors.green,
position: FlashPosition.top,
child: Container(
width: MediaQuery.of(context).size.width,
height: 70,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Shop Logo Updated Successfully",
style: const TextStyle(
color: Colors.white,
fontSize: 18,
),
),
],
)),
);
},
)
});
});
} on PlatformException catch (e) {
print("Failed to pick image: $e");
}
}
Here is the FutureBuilder I used to display the image:
FutureBuilder(
future: getData(),
builder: (BuildContext context, snapshot) {
imageUrl = (snapshot.data as Map<String, dynamic>)['shopLogo'];
if (snapshot.hasData) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
],
);
} else {
return Column(children: [
Container(
margin: EdgeInsets.fromLTRB(0, 10.0, 0, 20),
child: CircleAvatar(
radius: 50.0,
child: ClipRRect(
child: Image(
height: 100,
width: 100,
image: NetworkImage(imageUrl),
fit: BoxFit.fill,
),
borderRadius: BorderRadius.circular(50.0),
),
),
),
GestureDetector(
onTap: () {
showMyDialog();
},
child: Container(
margin: EdgeInsets.fromLTRB(0, 0, 0, 30),
child: Text(
"Change Logo Photo",
style: const TextStyle(
color: Colors.blue,
fontSize: 16,
),
),
),
),
]);
}
} else if (snapshot.hasError) {
return Text('no data');
}
return CircularProgressIndicator();
},
),
and here is my getData() function:
getData() async {
var firestore = FirebaseFirestore.instance;
DocumentSnapshot qn = await firestore
.collection('MerchantData')
.doc(widget.currentUser?.uid)
.get();
return qn.data();
}

Switch between streams flutter firebase

I have a class of complains in which there is a field of status. This status can be pending , inprogress, completed , rejected. In UI I have designed buttons to filter complaints on the basis of status.
The issue that I am facing is that when I switch stream on button action. It still contains the data of previous stream.
Can anyone help me to have 2 streams and not either of it contains data of previous stream.
bool _isSwitched = false;
List<Complains> complains = [];
final Stream<QuerySnapshot> _all = FirebaseFirestore.instance
.collection('Complains').orderBy("startTime", descending: true)
.snapshots();
final Stream<QuerySnapshot> _pending = FirebaseFirestore.instance
.collection('Complains').where('status', isEqualTo: 'Pending').orderBy("startTime", descending: true)
.snapshots();
ElevatedButton(onPressed:(){
setState(() {
_isSwitched = !_isSwitched;
complains.clear();
complains2.clear();
});
} , child: Text("Switch Stream"),),
StreamBuilder<QuerySnapshot>(
initialData: null,
stream: _isSwitched?_all:_pending,
builder: (context, snapshot) {
if (snapshot.hasError) {
Text("Error");
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
//length of stream is greater than 0
if (snapshot.data!.docs.length == 0) {
return Center(
child: Text("No Complains"),
);
}
for (var element in snapshot.data!.docs) {
Complains complain = Complains.fromMap(element.data() as Map<String, dynamic>);
_isSwitched?complains.add(complain):complains2.add(complain);
}
return ListView.builder(
itemBuilder: (context, index) {
return InkWell(
onTap: () {
},
child: Card(
margin: EdgeInsets.all(8.0),
elevation: 5,
color: Colors.white70,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12.0))),
child: Container(
padding: EdgeInsets.all(20),
child:
Column(children: [
Text("Title\n ${complains[index].title}",style: TextStyle(fontStyle: FontStyle.italic),),
Table(
children: [
TableRow(children: [
Text("Name: " ),
Text("Address: "+ complains[index].address.toString(),),
]),
TableRow(children: [
Text("Priority: "+ complains[index].priority.toString(),),
Text("Status: "+complains[index].status.toString(),),
]),
TableRow(children: [
Text("Worker: "+ complains[index].worker.toString(),),
Text("Service: "+ complains[index].service.toString(),),
]),
],
),
],)
),
),
);
},
itemCount: _isSwitched?complains2.length:complains.length,
);
},
),
),

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']);
},
);
}
});

using FutureBuilder to get Future<String> from firestore

This is my code in which I want to display an email which is a Future and I will get it from my firestore.However, I am not sure how I will need to retrieve my value using FutureBuilder which I want to use as a string.
This is my method to get my email:
Future<String> getEmail() async {
String _email = (await FirebaseAuth.instance.currentUser()).email;
DocumentSnapshot snapshot = await _firestore.collection('users')
.document(_email)
.collection('met_with')
.document('email')
.get();
// print("data: ${snapshot.data}"); // might be useful to check
return snapshot.data['email']; // or another key, depending on how it's saved
}
this is my updated code:
#override
Widget build(BuildContext context) {
return Card(
elevation: 3.0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
child: ListTile(
leading: CircleAvatar(
backgroundImage: AssetImage(imagePath),
),
trailing: Icon(Icons.more_horiz),
title: Text(
email,
style: TextStyle(
c #override
Widget build(BuildContext context) {
return Card(
elevation: 3.0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
child: ListTile(
leading: CircleAvatar(
backgroundImage: AssetImage(imagePath),
),
trailing: Icon(Icons.more_horiz),
title: Text(
email,
style: TextStyle(
color: Colors.deepPurple[700],
fontWeight: FontWeight.bold,
),
),
subtitle: Text(infection),
onTap: () => showModalBottomSheet(
context: context,
builder: (builder) {
return FutureBuilder(
future: getEmail(),
builder: (BuildContext context, snapshot) {
if(snapshot.hasData){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: CircularProgressIndicator(),
);
}else{
return Padding(padding: EdgeInsets.symmetric(vertical: 50.0, horizontal: 10.0),
child: Column(
children: <Widget>[
BottomSheetText(
question: 'Email', result: snapshot.data['email']),
SizedBox(height: 5.0),
BottomSheetText(
question: 'Contact Time',result:"lol"),// getTimeStamp().toString()),
SizedBox(height: 5.0),
BottomSheetText(
question: 'Contact Location',
result: "help"),
SizedBox(height: 5.0),
BottomSheetText(question: 'Times Contacted', result: "lool",),
],
),
);
}
}else{
return CircularProgressIndicator();}
}
);
}
),
),
);
}
}
Here is my firebase database:
enter image description here
Your query is wrong, try following one.
Future<String> getEmail() async {
String _email = (await FirebaseAuth.instance.currentUser()).email;
var a = await Firestore.instance
.collection("met_with")
.where('email', isEqualTo:. _email )
.getDocuments();
return a.documents[0]['email'];
}
And to call this method you need futureBuilder.
FutureBuilder(
future: getEmail(),
builder: (BuildContext context, snapshot) {
if(snapshot.hasData){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: CircularProgressIndicator(),
);
}else{
return Center( // here only return is missing
child: Text(snapshot.data['email'])
);
}
}else if (snapshot.hasError){
return Text('no data');
}
return CircularProgressIndicator();
},
),
You need to use a FutureBuilder:
FutureBuilder(
future: getEmail(),
builder: (BuildContext context, snapshot) {
if(snapshot.hasData){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: CircularProgressIndicator(),
);
}else{
return Center( // here only return is missing
child: Text(snapshot.data['email'])
);
}
}else if (snapshot.hasError){
Text('no data');
}
return CircularProgressIndicator();
},
),
This way you can use the returned value of the method inside the build method. Also change the Future method to the following:
Future<String> getEmail() async {
String _email = (await FirebaseAuth.instance.currentUser()).email;
return await _firestore.collection('users')
.document(_email)
.collection('met_with')
.document('email')
.get();
}