How to check if a document is empty - firebase

I have the following empty document:
As I understand it, the document exists, but it's empty.
I'm showing the user a list of all document ID, then allowing him to choose the document to display data for.
When the user chooses this empty document, an error appears because i called length on null object in my listview. That's fair.
I want to show the user an alert dialog informing him that it's empty.
child: FutureBuilder(
future: snapshot,
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
if (snapshot.hasData) {
if(snapshot.data.data.length == 0) {
return AlertDialog(
content: Text('Wrong Date', textAlign: TextAlign.center, style: TextStyle(color: Colors.black)),
backgroundColor: Colors.redAccent,
actions: [Icon(Icons.keyboard_backspace)],
);
}
This is what I'm going with at the moment. Just checking the length of the document.
That does not seem right to me.
Is there any other way of doing this?

Looking at the API for DocumentSnapshot, the data() method returns a Map of fields. If you want to know if there are no fields, simply ask the Map if its length is 0.
snapshot.data().length == 0

Related

The argument type 'List<CommentData>' can't be assigned to the parameter type 'List<Widget>

I'm trying to build the the listview with the data queried from firebase. But I'm having an error 'The argument type 'List < CommentData >' can't be assigned to the parameter type 'List< Widget >' The code is as follows
Widget buildComments() {
if (this.didFetchComments == false) {
return FutureBuilder<List<CommentData>>(
future: commentService.getComments(),
builder: (context, snapshot) {
if (!snapshot.hasData)
return Container(
alignment: FractionalOffset.center,
child: CircularProgressIndicator());
this.didFetchComments = true;
this.fetchedComments = snapshot.data;
return ListView(
children: snapshot.data, // where i'm having error
);
});
} else {
return ListView(children: this.fetchedComments);
}
}
How can i work around this..
The ListView expects a List<Widgets> but you are passing List<CommentData>
You can modify your ListView to the following to rectify the error.
ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Text(snapshot.data[index]['key']); //Any widget you want to use.
},
);
The error speak itself
The argument type 'List<CommentData>' can't be assigned to the parameter type 'List<Widget>'
If you want to create List Text widget for showing comment, you can use
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) => Text(snapshot.data[index].*), //What ever you want to show in from your model
);
The snapshot.data returns List<CommentData> while ListView's children requires a List of Widgets, thus you get that error.
Try changing
return ListView(
children: snapshot.data,
);
to something like:
return ListView(
children: Text(snapshot.data[index].userName), //change userName to whatever field of CommentData class you want to show
);
I would recommend using ListView.Builder to work with Lists and indexes.

StreamBuilder with flutter_firebase has null value on second widget build (image disappears)

I managed to show images from firestore using streambuilder on a page, but the problem is that the image disappears (I get a null snapshot.data value) if I go back to the last page and come back.
Here is my code. How can I make the snapshot data persist, so the image stays there and doesn't disappear when the widget rebuilds?
Container(child: Column(
children: [
Text(' Certifications',
Container(child: StreamBuilder(
stream: certificates,
builder: (context, snapshot) {
return !snapshot.hasData
? Center(child: Container(
child: Center(child: Text(
'No images yet'))))
: Container(
child: GridView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
url = snapshot.data.documents[index].get('url');
return Container(child: FadeInImage
.memoryNetwork(fit: BoxFit.cover,
placeholder: kTransparentImage,
image: url),
),
);
}),
);
}),
),
Streams are asynchronous. which means StreamBuilder does not get old snapshots, only new future snapshots. This is your problem.
When your widget is re-built it is subscribing to a stream that has already had events. Yes you would think that data in the snapshot should be the last event value, but that is not the case. It will be null until a new event is pushed onto your certificates stream.
So one solution is for the service that is loading your certificates to store the value of the initial API request and make that available for you to use in your StreamBuilder's initialData property.
I would structure it like this:
StreamBuilder(
stream: certificateService.stream,
initialData: certificateService.value, // <-----
builder: ...
)
Hope this points you in the right direction.

Error showing when document is deleted from firestore with streambuilder (flutter)

Hello everybody first of all I'm sorry for my English if its not clear, I'm working on a personal project. so, I'm using StreamBuilder on firestore document with the userID of the user from 'Users' Collections. So, I have retrieved the "imageUrl" field and display it in Image Network in my application, so, I have 'Delete Account' Button, this button will delete the account from firebase auth and also delete the document that the streambuilder listens to it.
So, the error happens because the streambuilder will build ImageNetwork and retrieve the URL from the document field.
Any ideas to handle the error?
this is the code for the streamBuilder that will return NetworkImage
StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection('Users')
.document(user.getID())
.snapshots(),
builder:
(context, AsyncSnapshot<DocumentSnapshot> snapshot) {
print(snapshot.connectionState);
var userDocument = snapshot.data;
if (userDocument.data.length == 0) {
return const Center(
child: Text(
"Not Available",
style:
TextStyle(fontSize: 30.0, color: Colors.grey),
),
);
} else
return AvatarGlow(
glowColor: Colors.redAccent,
endRadius: 90,
child: Material(
elevation: 8.0,
shape: CircleBorder(),
child: CircleAvatar(
backgroundColor: Colors.grey[100],
child: ClipOval(
child: FadeInImage(
image: NetworkImage(
userDocument['imageUrl'] ??
'https://picsum.photos/250?image=9'),
placeholder: AssetImage('assets/noImage.png'),
),
),
radius: 70,
),
),
);
},
),
Debug error
The getter 'length' was called on null.
Receiver: null
Tried calling: length
The relevant error-causing widget was
StreamBuilder<DocumentSnapshot>
The solution was on if else blocks
StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection('Users')
.document(user.getID())
.snapshots(),
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.data != null && snapshot.data.exists) {
var userDocument = snapshot.data;
// return something
}
}
For a non-existing document userDocument.data will return null, so userDocument.data.length throws the error you get.
My guess is you want to check if the document exists, which you'd do with:
if (userDocument.exists) {
Also see the reference documentation on DocumentSnapshot class, which is the type of object your userDocument is.
Ok. So, how these StreamBuilder(s) and FutureBuilder(s) are supposed be used are as follows:
Note: the following code should be inside your Builder function.
if(snapshot.hasData){
// Your normal functioning of the app will follow here.
// Now that you know that your snapshot contains data,
// then you access the data.
var userDocument = snapshot.data;
// Now, you can check if the userDocument exists or not.
}
else if(snapshot.hasError){
// return an error message or do something else.
}
// return any default Widget while the data is being loaded.
return CircularProgressIndicator();
Also, I would recommend that once the user requests to delete his/her account, you should navigate back to the home screen...

How to show a text widget when a screen is not calling data from Firebase

I have the activity screen that gets data from Firebase, I'm trying to show a text message like "No activities yet" when the database is not providing data.
I tried to use a ternary operator condition that checks if the database is empty and it works, however, the problem is that when I go the activity screen and it has data, it still shows the message for about 1 second and then shows the activity screen, so, I would like to know if there is a better way to implement this.
Hope this makes sense.
See the code below
#override
Widget build(BuildContext context) {
return Scaffold(
body: RefreshIndicator(
onRefresh: () => _setupActivities(),
child: _activities.isEmpty
? Center(
child: Text(
'Aun no Tienes Notificaciones',
style: ktextTitlesStyle.copyWith(
color: Colors.black,
fontSize: responsive.height(context) / 50),
),
)
: ListView.builder(
itemCount: _activities.length,
itemBuilder: (BuildContext context, int index) {
Activity activity = _activities[index];
return _buildActivity(activity);
},
),
),
);
}
}
I'm not sure I fully understand what you are trying to accomplish but checking for .hasData on the AsyncSnapshot might help determine if data is returned.
Container( child: FutureBuilder(future: someFutureFirebaseDataReturningMethod(), builder: (context, snapshot) {
if (!snapshot.hasData) return _noActiviesYet();
return _showActivies(snapshot, context);
}))
.hasData returns "whether this snapshot contains a non-null data value."(https://api.flutter.dev/flutter/widgets/AsyncSnapshot/hasData.html)

Can I listen to a single document in Firestore with a StreamBuilder?

I'm trying to listen to changes in a single document of a collection, but I can't get it to work.
Widget build(BuildContext context) {
return StreamBuilder(
stream: Firestore.instance.collection("events").document(widget.documentID).get().asStream(),
...
}
}
I'm using the .asStream() method, but I'm only getting the document once. If I change the data in the Firebase console, nothing updates unless I reopen the view.
Is there a way to do this?
The reason you are only getting the document once is because the DocumentReference.get method originally only returns a Future and using asStream will only return that one single Future to the Stream.
The cloud_firestore package, however, has a built-in method of listening to documents properly.
You can use DocumentReference.snapshots instead, which returns a Stream of every change of that document.
In your code, you will only have to replace .get().asStream() by .snapshots().
if you want to get a single document use Future builder instead of Stream Builder.
FutureBuilder<DocumentSnapshot>(
future: Firestore.instance
.collection('users')
.document(widget.puid)
.get(),
builder: (context,
AsyncSnapshot<
DocumentSnapshot>
snapshot) {
if (snapshot.hasError)
return Center(
child: Text(snapshot
.hasError
.toString()),
);
return snapshot.hasData
? Text(
"${snapshot.data['username']}",
style: TextStyle(
color:
kPrimaryColor,
fontSize: 18,
fontWeight:
FontWeight
.bold),
)
: Container();
},
),

Resources