I'm making this realy simple project where when you click a button, it increments a value in the realtime databse. This works, but I want a Text widget to update everytime the value changes. I've really new to this so please excuse my lack of knowledge. I have noticed that there's no snapshots methods available that would let me do something like this, so how I update a label to be the data in the realtime storage eveyime it changes? Here's the code which I use to increment the values-
Future<void> addData(int id) async {
DatabaseReference pointsRef = FirebaseDatabase.instance.ref("$id");
DataSnapshot snapshot = await pointsRef.get();
pointsRef.set((snapshot.value as int) + 1);
}
Use StreanBuilder like so:
StreamBuilder(
stream: FirebaseDatabase.instance.ref("id").onValue,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return Text(snapshot.data.toString());
},
)
Related
I'm making my first Flutter app and I encounter a problem and doesn't found any solution for it.
I have a view where I render a Firestore document, and there is two ways of getting there:
From a list where I already loaded my documents
From Dynamic Links with uid attached as arguments (args)
So in order to listen document changes and loading the data when arriving from the link I used FirestoreBuilder like this:
return FirestoreBuilder<EventDocumentSnapshot>(
ref: eventsRef.doc(args.uid),
builder: (context, AsyncSnapshot<EventDocumentSnapshot> snapshot, Widget? child) {
if (!snapshot.hasData) {
return Container();
}
Event? event = snapshot.requireData.data;
return Scafold(); //Rest of my rendering code
}
);
How I could avoid first call to Firebase when I already have the data but still listen to changes? The main problem is that my hero animation doesn't work because of this.
I tried with a StreamBuilder and initialDataparam but since it's expecting stream I didn't know how to cast my data.
Okay, so I found the solution myself after many tries, so I added my Model object that can be null as initialData, but the thing that makes me struggle with is how you get the data in the builder. You have to call different methods depending on where the data is coming from.
return StreamBuilder(
initialData: args.event
ref: eventsRef.doc(args.uid),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
// Here is the trick, when data is coming from initialData you only
// need to call requireData to get your Model
Event event = snapshot.requireData is EventDocumentSnapshot ? snapshot.requireData.data : snapshot.requireData;
return Scafold(); //Rest of my rendering code
}
);
Reading through cloud_firestore's documentation you can see that a Stream from a Query can be obtained via snapshots()
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('books').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return new Text('Loading...');
return new ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document['title']),
subtitle: new Text(document['author']),
);
}).toList(),
);
},
);
This won't help you, but with GetX it's simple to implement like this: You don't need StreamBuilder anymore.
//GetXcontroller
class pageController extends GetXcontroller {
...
RxList<EventModel> events = RxList<EventModel>([]);
Stream<List<EventModel>> eventStream(User? firebaseUser) =>
FirebaseFirestore.instance
.collection('events')
.snapshots()
.map((query) =>
query.docs.map((item) => UserModel.fromMap(item)).toList());
#override
void onReady() async {
super.onReady();
events.bindStream(
eventStream(controller.firebaseUser)); // subscribe any change of events collection
}
#override
onClose() {
super.onClose();
events.close(); //close rxObject to stop stream
}
...
}
You can use document snapshots on StreamBuilder.stream. You might want to abstract the call to firebase and map it to an entity you defined.
MyEntity fromSnapshot(DocumentSnapshot<Map<String, dynamic>> snap) {
final data = snap.data()!;
return MyEntity (
id: snap.id,
name: data['name'],
);
}
Stream<MyEntity> streamEntity(String uid) {
return firebaseCollection
.doc(uid)
.snapshots()
.map((snapshot) => fromSnapshot(snapshot));
}
return StreamBuilder<MyEntity>(
// you can use firebaseCollection.doc(uid).snapshots() directly
stream: streamEntity(args.uid),
builder: (context, snapshot) {
if (snapshot.hasData) {
// do something with snapshot.data
return Scaffold(...);
} else {
// e.g. return progress indicator if there is no data
return Center(child: CircularProgressIndicator());
}
},
);
For more complex data models you might want to look at simple state management or patterns such as BLoC.
I'm currently using the Firebase realtime database and a stream builder to listen to changes like so:
late var stream = FirebaseDatabase.instance
.reference()
.child("devices")
.child(user_id);
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<Event>(
stream: stream.onValue,
builder: (context, snapshot) {
print('stream updated');
if (snapshot.hasData &&
!snapshot.hasError &&
snapshot.data?.snapshot.value != null) {
var data = snapshot.data!.snapshot.value;
handler.updateData(data);
}
return Column(
When the widget first builds, it works completely fine so I'm able to get back the relevant data. However, when I make a change in the database, the values don't update. What am I doing wrong?
The structure I'm using for my data is Devices -> user_id -> data if thats relevant.
edit:
The problem wasn't with my code but flutter web not updating until the browser window was in focus.
I want to check whether collection with this username and account type exists, it means I want to see if user is premium.
The output when app runs is:
ok
user
ok
model
Why does it print 'ok' twice and it looks like snapshot both has and hasn't any data?
Here is part of the code, if it doesn't say anything I will provide full class:
#override
Widget build(BuildContext context) {
return Scaffold(
body: isLoading
? Container(
child: Center(child: CircularProgressIndicator()),
)
: StreamBuilder(
stream: Firestore.instance
.collection('users')
.where('email', isEqualTo: email)
.where('account', isEqualTo: 'model')
.snapshots(),
builder: (context, snapshot) {
print('ok');
if (!snapshot.hasData) {
box.put('account', 'user');
print(box.get('account'));
} else {
box.put('account', 'model');
print(box.get('account'));
}
return Container(...
Thank you in advance and maybe there is easiest way to see if collection with such data exists?
As far as I can see this is working as intended. When your widget is first rendered, it starts loading the data for the stream from Firestore. At that point snapshot.hasData is still false, so it renders your widget with the if block.
Then when the data becomes available, the stream gets updated, and that triggers the widget to be rendered again. At this point, the snapshot.hasData is true, so it renders your widget with the else block.
I have set a stream from database that I created manually on firebase with some sample fields, when I use streamBuilder inside a stateful widget the snapshot.data return nothing/null.
final CollectionReference **storeCollection** = Firestore.instance.collection('stores');
Stream**<List<Stores>>** get **stores** {
return **storeCollection.snapshots()**.map(_storeListFromSnapshot);
Then after I used a StreamBuilder to get snapshot.data but it returns null
#override
Widget build(BuildContext context) {
return **StreamBuilder**<Object>(
stream: DatabaseService().**stores**,
builder: (context, **snapshot**) {
**List<Stores> stores** = **snapshot.data** ?? []; //returns null on this line
I was able to update data to firebase with storeCollection.document(uid).setData()
Usually an AsyncSnapshot is received at the beginning of the listening to signal that the subscription is active and it's waiting for something and it does not contain data.
You can find more checking out the docs about the ConnectionState enum which denotes the state of an AsyncSnapshot.
That said, the most basic thing you can do with the builder of your StreamBuilder is this:
builder: (context, snapshot){
if (snapshot.hasData){
List<Stores> stores = snapshot.data;
//the rest of your logic on stores
}else{ //nothing yet, we're waiting for the event, show a loader or whatever
return Center(child: CircularProgressIndicator());
}
}
I am new to firebase/firestore and wanted to add firestore to my app. My app currently has a login and adds data to the database with the UID set as the document name. Console Image
I want to display the name in my apps profile page. How would I achieve this?
Called it with this
Center(child:building(context),),
Widget building(BuildContext context) {
return new StreamBuilder(
stream: Firestore.instance
.collection('UserData')
.document(getUID())
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return new Text("Loading");
} else {
return new Text(snapshot.data.toString());
}
});
}
Current Error
Error Image
Previous Error
Error Message
Thanks in advance!
Try this
Widget building(BuildContext context) {
return new StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection('UserData')
.document('TjMJDFd940UtLORgdYND771GYwG2')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return new Text("Loading");
} else {
Map<String, dynamic> documentFields = snapshot.data.data;
return Text(documentFields["First Name"] +
" " +
documentFields["Last Name"]);
}
});
}
Note that TjMJDFd940UtLORgdYND771GYwG2 refer to documentID.
The docs for the Flutter Firebase API are stashed away and are admittedly hard to find. Here's the documentation on the QuerySnapshot class.
You are trying to look at the .document property of a QuerySnapshot object, so it's throwing an error because that property does not exist. Try using snapshot.documents to get a list of documents to iterate over (or just use snapshot.documents[0] if there will always only be one), and then read the data from that, i.e.: snapshot.documents[0].data[documentId]['First Name']. I removed quotes from documentId reference, since we want to index the variable value and not just the string "documentId".