Stream builder returning error screen when there is no data existing in Firebse Firestore - google-cloud-firestore

I created a function that returns a widget. My firestore has no data.
here is the code for that function:
Widget _getImage() {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(100.0),
child: SizedBox(
height: 48,
width: 48,
child: StreamBuilder(
stream: UserImages.snapshots(),
builder: (context, snapshot) {
if (snapshot.data != null) {
String profileImageURL =
snapshot.data!.get('passport URL')?.toString() ?? "";
// if profileImageURL empty, use holder image
if (profileImageURL == "") {
profileImageURL =
"https://firebasestorage.googleapis.com/v0/b/[name of my firebase project].appspot.com/o/app_assets%2FprofileHolder.png?"; //another default image
}
return CachedNetworkImage(
imageUrl: profileImageURL,
progressIndicatorBuilder:
(context, url, downloadProgress) =>
CircularProgressIndicator(
value: downloadProgress.progress,
color: Theme.of(context).primaryColor,
),
errorWidget: (context, url, downloadProgress) =>
const Icon(Icons.error_outline),
);
} else
return SizedBox();
}),
),
),
);
}
I want the StreamBuilder to return a different widget in case there is no data in Firestore and the user has not yet uploaded the profile image.

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: get a document from Firestore inside a widget that returns a Widget

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?

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

How to create carousel slider with firestore image and onclick launch url in flutter?

I want to create carousel slider in flutter with cloud firestore. I created cloud firestore collection with the name of "slider" and i have two fields one is "image" and another one is "url".
Now i need to stream firestore collection in my carousel slider and when user click image, want to launch url.
My Carousel Slider Code
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
class Dashboard extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
SizedBox(height: 15.0),
CarouselSlider(
options: CarouselOptions(
height: 400.0,
enlargeCenterPage: true,
autoPlay: true,
aspectRatio: 16 / 9,
autoPlayCurve: Curves.fastOutSlowIn,
enableInfiniteScroll: true,
autoPlayAnimationDuration: Duration(milliseconds: 800),
viewportFraction: 0.8),
items: [
Container(
margin: EdgeInsets.all(5.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
image: DecorationImage(
image: AssetImage('$show Firestore image'),
onPressed: () {
launchURL(
"$ launch firestore url");
}
fit: BoxFit.cover,
),
),
),
],
),
],
);
}
}
Can anyone guide me?
Widget build(BuildContext context) {
var idx = 1;
return Container(
margin: EdgeInsets.only(top: 4.0, bottom: 8.0),
height: getProportionateScreenHeight(150),
width: double.infinity,
decoration: BoxDecoration(
color: Color(0xFF4A3298),
borderRadius: BorderRadius.circular(20),
),
child:StreamBuilder(
stream: FirebaseFirestore.instance.collection(BANNER_URL).snapshots(),
builder: (context, AsyncSnapshot snapshot) {
List list = []..length;
switch (snapshot.connectionState) {
case ConnectionState.none:
return Container(
child: Center(
child: new Text(
'No network. \nPlease, check the connection.')),
);
break;
case ConnectionState.waiting:
return Container(
child: Center(child: new CircularProgressIndicator()),
);
break;
default:
if (snapshot.hasError) {
return Container(
child: Center(
child: Text(snapshot.error.toString()),
),
);
} else if (snapshot.hasData) {
for (int i = 0; i < snapshot.data.size; i++) {
debugPrint("Index is " + idx.toString());
list.add(NetworkImage(
snapshot.data.docs[i].data()['image_url']));
idx++;
}
return ClipRect(
child: Banner(
message: "Publicite aqui",
location: BannerLocation.topEnd,
color: Colors.red,
child: Carousel(
boxFit: BoxFit.cover,
images: list,
autoplay: true,
animationCurve: Curves.fastLinearToSlowEaseIn,
animationDuration: Duration(milliseconds: 2000),
dotSize: 2.0,
dotColor: AppTheme.cuyuyuOrange400,
dotBgColor: AppTheme.cuyuyuTransparent,
indicatorBgPadding: 2.0,
)),
);
}
}
}),
);
}
You can make use of a FutureBuilder to fetch the document snapshot, and on completion, you can store the URLs in a list and use the list for the Carousel
Example code which uses FutureBuilder to fetch a list of urls:
Future getCarouselWidget() async {
var firestore = Firestore.instance;
QuerySnapshot qn = await firestore.collection("carousel").getDocuments();
return qn.documents;
}
Widget build(BuildContext context) {
var idx = 1;
return Container(
child: FutureBuilder(
future: getCarouselWidget(),
builder: (context, AsyncSnapshot snapshot) {
List<NetworkImage> list = new List<NetworkImage>();
if (snapshot.connectionState == ConnectionState.waiting) {
return new CircularProgressIndicator();
} else {
if (snapshot.hasError) {
return new Text("fetch error");
} else {
//Create for loop and store the urls in the list
for(int i = 0; i < snapshot.data[0].data.length; i++ ) {
debugPrint("Index is " + idx.toString());
list.add(NetworkImage(snapshot.data[0].data["img_"+idx.toString()]));
idx++;
}
return new Container(
height: 250.0,
child: new Carousel(
boxFit: BoxFit.cover,
images: list, <== Set the list here
autoplay: true,
dotSize: 4.0,
indicatorBgPadding: 4.0,
animationCurve: Curves.fastOutSlowIn,
animationDuration: Duration(milliseconds: 1000),
));
}
}
}),
);

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