Using Stream Building with a specific Firestore document - firebase

I am building a Flutter application and I am having trouble understanding how to implement Firestore. Out of the tutorials I have seen, I only see how to create a snapshot of an entire collection, however in my case, my collection is users, so I only need to snapshot the document of a particular user. There doesn't appear to be documentation on the Firebase docs on how to do this nor is there much documentation on the FlutterFire GitHub page. Please help!
This is the Widget I'm trying to build with StreamBuilder.
#override
Widget build(BuildContext context) {
return new StreamBuilder(
stream: Firestore.instance.collection('users').document(userId).snapshots(),
builder: (context, snapshot) {
return new ListView.builder(
itemCount: //what do I put here?,
itemBuilder: (context, index) => new Item(//And here?),
);
}
);
}

Lets say you want to create a Text with the name parameter from your document
Widget build(BuildContext context) {
String userId = "skdjfkasjdkfja";
return StreamBuilder(
stream: Firestore.instance.collection('users').document(userId).snapshots(),
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData) {
return Text("Loading");
}
var userDocument = snapshot.data;
return Text(userDocument["name"]);
}
);
}
This is just one instance. Creating a StreamBuilder on the document will rebuild itself every time the document itself is changed. You can try this code, and then go to your console and change the "name" value. Your app will automatically reflect the changes.
Instead of just one Text, you could build entire tree that uses data from your stream.
If you want to get just at the moment value of the document, you can do so by resolving the Future of get() method on document reference.
var document = await Firestore.instance.collection('users').document(userId).get(),

Each element should be casted to have a reference later in the code.
return new StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance.collection('users').document(userId).snapshots(), //returns a Stream<DocumentSnapshot>
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData) {
return new Text("Loading");
}
var userDocument = snapshot.data;
return new Text(userDocument["name"]);
}
);
}

Update 2023 with null safety
class _UserInformationState extends State<UserInformation> {
final _usersStream = FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid) // πŸ‘ˆ Your document id change accordingly
.snapshots();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: StreamBuilder<DocumentSnapshot<Map<String, dynamic>>>(
stream: _usersStream,
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}
Map<String, dynamic> data =
snapshot.data!.data()! as Map<String, dynamic>;
return Text(data['fullName']);
},
),
),
);
}
}

Related

Is there anyway that I can check the Documents Snapshot FirebaseFireStore flutter

I m trying to snapshot the Collection of users and I want to check if the user has the field or not
If the users has then show this
If user doesn’t have then show this
This is the code i got so far.
class _HalfScreenState extends State<HalfScreen> {
final userUid = FirebaseAuth.instance.currentUser!.uid;
#override
Widget build(BuildContext context) {
return StreamBuilder<DocumentSnapshot?>(
stream: FirebaseFirestore.instance
.collection("users")
.doc(userUid)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.data != null) {
return const Text("Loading...");
}
return Text(
(snapshot.data as DocumentSnapshot)['groupId'],
),
});
}
}
Step by step that'd be:
Get the data from the document as a Map.
Check if the map contains a key for the field you're looking for.
In code that'd be something like this:
// πŸ‘‡ Indicate the type of the data in the document
return StreamBuilder<DocumentSnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance
.collection("users")
.doc(userUid)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.data != null) {
return const Text("Loading...");
}
// πŸ‘‡ Get the data (Map<String, dynamic>
var data = snapshot.data.data();
return Text(
// πŸ‘‡ Check if the field is tere
data.containsKey('groupId') ? 'Yes, it's there' : 'Nope, field not found'
),
}
);

How to retreive data from firestore flutter

I'm new into flutter and firebase integrations and I'm having some troubles to retreive all the data from the firebase collection.
I have tried this method:
getCollection() {
CollectionReference coleccion =
FirebaseFirestore.instance.collection('materias');
return Container(
child: StreamBuilder(
stream: coleccion.doc('aprobadas').snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
return Text(snapshot.data.data()['codigo'],
style: TextStyle(fontSize: 50, color: Colors.white));
} else {
return CircularProgressIndicator();
}
},
),
);
}
Now I'm a little bit frustrated because I have tried a differents methods and doesn't work.
I really appreciate all the help.
Best regards
Data can be retrieved using the below code from firestore to flutter.
One-time Read
call the Query.get or DocumentReference.get methods
class GetUserName extends StatelessWidget {
final String documentId;
GetUserName(this.documentId);
#override
Widget build(BuildContext context) {
CollectionReference users = FirebaseFirestore.instance.collection('users');
return FutureBuilder<DocumentSnapshot>(
future: users.doc(documentId).get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
return Text("Document does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data!.data() as Map<String, dynamic>;
return Text("Full Name: ${data['full_name']} ${data['last_name']}");
}
return Text("loading");
},
);
}
}
Realtime changes
FlutterFire provides support for dealing with real-time changes to collections and documents. A new event is provided on the initial request, and any subsequent changes to collection/document whenever a change occurs (modification, deleted or added).
Both the CollectionReference & DocumentReference provide a snapshots() method which returns a Stream:
Stream collectionStream = FirebaseFirestore.instance.collection('users').snapshots();
Stream documentStream = FirebaseFirestore.instance.collection('users').doc('ABC123').snapshots();
Please refer official documentation here
You can use a StreamBuilder. That will be easy to understand.
StreamBuilder(
stream: FirebaseFirestore.instance.collection("collection").snapshot,
builder: (BuildContext context,snapshot) {
if(snapshot.hasdata!=true) {
return CircularProgressIndicator();
} else {
return ListView.builder(
itemcount:snapshot.data.docs.length,
builder(context,index) {
return Text(snapshot.data.docs[index].data()["filedname"]);
}
}
)

Flutter: Firestore Get User uid inside StreamBuilder

I have an app which I want to display documents inside collection.. the collection reference is the uid of the user.
Is there a way to get current user uid and put this uid inside StreamBuilder in stream.
I have tried like so but it did not work and returned null:
class _MyAdsState extends State<MyAds> {
final FirebaseAuth _auth = FirebaseAuth.instance;
Future getCurrentUser() async {
final FirebaseUser user = await _auth.currentUser();
final uid = user.uid;
print(uid);
return uid.toString();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("${getCurrentUser()}").snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> querySnapShot){
if(querySnapShot.hasError){
return Text('Some Error');
}
if(querySnapShot.connectionState == ConnectionState.waiting){
return CircularProgressIndicator();
}else{
final list = querySnapShot.data.documents;
return ListView.builder(
itemBuilder: (context, index){
return ListTile(
title: Text(list[index]["subject"]),
subtitle: Text(list[index]["category"]),
);
},
itemCount: list.length,
);
}
},
)
Getting the UID is an asynchronous operation, so requires a FutureBuilder.
If you want to use the UID to then build a stream, you'll need to have a FutureBuilder for the UID, and then inside of that a StreamBuilder for the stream from the database.
body: FutureBuilder(
future: FirebaseAuth.instance.currentUser(),
builder: (context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.hasData) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection(snapshot.data.uid).snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> querySnapShot){
...
},
)
}
else {
return Text('Loading user data...');
}
THANK YOU GUYS!
I was looking for this for too long now. I had the "problem" that I was recording the senderUID for a sent message only, but of course wanted the Name being displayed in the "sentFrom" field. So I had to query Firestore for the UID and pull out the email. My solution:
FutureBuilder<QuerySnapshot>(
future: _firestore.collection("users").get(),
builder: (context, futureSnapshot) {
if (!futureSnapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
Map<String, String> users = {};
final userData = futureSnapshot.data.docs;
for (var user in userData) {
users[user.id] = user.data()["email"];
}
return StreamBuilder<QuerySnapshot>(
stream: _firestore.collection("messages").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
// ignore: missing_return
final messages = snapshot.data.docs;
List<Widget> messageWidgets = [];
for (var message in messages) {
final messageText = message.data()["text"];
final messageEmail = users[message.data()["senderUID"]];
messageWidgets
.add(Text("$messageText from $messageEmail"));
}
return Column(children: messageWidgets);
},
);
},
),
I just created a map from the data and used it inside the stream builder. Is there maybe a better solution?

How to query data from Firebase with an async call to uid

I'm trying to fetch data that belong to the logged in user, however, the "getuserui" is async, for some reason. even though the user is logged in to do stuff inside the app the function still returns a Future....
I've lost count to how many different things i've tried, including .then and such but here's my latest attempt.
#override
Widget build(BuildContext context) {
return SizedBox(
height: 900,
child: StreamBuilder(
stream: () async{
fireFirestore.instance.collection('properties').where('uid', isEqualTo: await _authService.getUserId()) .snapshots(),
},
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData)
return const Text('Loading...');
else {
return ListView.builder( ...............
in case you need to see the getUserId():
Future<String> getUserId() {
return _auth.currentUser().then((value) => value.uid.toString());
}
(i've done this method in both the future way (.then) and the async way (async await)
it just tells me the argument type Future<null> can't be assigned to the parameter type Stream
First, you're passing an async function as a stream, hence your error. Second, you need to wrap your StreamBuilder in a FutureBuilder since it depends on the future _authService.getUserId().
#override
Widget build(BuildContext context) {
return SizedBox(
height: 900,
child: FutureBuilder(
future: _authService.getUserId(),
builder: (context, snapshot) {
if(snapshot.hasData)
return StreamBuilder(
stream: fireFirestore.instance.collection('properties').where('uid', isEqualTo: snapshot.data) .snapshots(),
builder: (context, snapshot) {
...
},
);
return Text('future had no data');
},
),
);
}

How to efficiently access a firestore reference field's data in flutter?

Using similar code as flutter's firestore example, suppose there is a reference field stored in a snapshot document, called: document['userRef'].
First of all, how do I access the data of userRef? Using document['userRef'].get().data or document['userRef'].get().username I wasn't able to access the data. (NoSuchMethodError: Class 'Future<DocumentSnapshot>' has no instance getter 'data')
I also tried using document['userRef'].get().then(...) but getting the error: type 'Future<dynamic>' is not a subtype of type 'String'
Even if .then would work, wouldn't it then look up the same reference again for each message? Here the database is updated in realtime, but it's unnecessary to make the same lookup for multiple messages in the ListView.
class MessageList extends StatelessWidget {
MessageList({this.firestore});
final Firestore firestore;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: firestore.collection('messages').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
final int messageCount = snapshot.data.documents.length;
return ListView.builder(
itemCount: messageCount,
itemBuilder: (_, int index) {
final DocumentSnapshot document = snapshot.data.documents[index];
// document['userRef'] exists here
return ListTile(
title: Text(document['message'] ?? '<No message retrieved>'),
subtitle: Text('Message ${index + 1} of $messageCount'),
);
},
);
},
);
}
}
Edit:
I was able to fetch the nested data using FutureBuilder, though not sure how efficient it is. (Wouldn't this possibly send loads of redundant requests to Firebase?)
Creating a widget for the nested data, where document['userRef'] exists:
FutureBuilder(
future: userData(document['userRef']),
builder: (BuildContext context,
AsyncSnapshot<dynamic> uData) {
return Text(uData.data['username']);
},
);
And the userData function looks like this:
Future<dynamic> userData(DocumentReference user) async {
DocumentSnapshot userRef = await user.get();
return userRef.data;
}
Sticking to the Firebase and Flutter way, it is possible to use a Streambuilder inside a Streambuilder. That is, instead of using a FutureBuilder for the nested data, which makes you wait for each .get request.
(The code is untested, but the principle is tested.)
class MessageList extends StatelessWidget {
MessageList({this.firestore});
final Firestore firestore;
#override
Widget build(BuildContext context) {
Map UserSnapshot = Map(); // create a variable for accessing users by id
return StreamBuilder<QuerySnapshot>(
stream: firestore.collection('users').snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> UsersSnapshot) {
// process usersnapshot from list to map
UsersSnapshot.data.documents.forEach((userRecord) {
//print(optionRecord.documentID); // debug
UserSnapshot[userRecord.documentID] = userRecord;
});
// user data can be accessed as soon as there is a reference field or documentID:
// UserSnapshot[document['userRef']]['userName'}
return StreamBuilder<QuerySnapshot>(
stream: firestore.collection('messages').snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> MessagesSnapshot) {
if (!MessagesSnapshot.hasData) return const Text('Loading...');
final int messageCount = MessagesSnapshot.data.documents.length;
return ListView.builder(
itemCount: messageCount,
itemBuilder: (_, int index) {
final DocumentSnapshot document =
MessagesSnapshot.data.documents[index];
// document['userRef'] exists here
// UserSnapshot[document['userRef']]['userName'} is accessible here
return ListTile(
title:
Text(document['message'] ?? '<No message retrieved>'),
subtitle: Text('Message ${index + 1} of $messageCount'),
);
},
);
},
);
});
}
}

Resources