Flutter StreamBuilder not updating when data is changed - firebase

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.

Related

How to show real time data from from realtime database?

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

How to show Flutter firebase realtime database on widgets with data updates using Stream builder?

I am using Firebase for first time. My app is connected to firebase successfully and i am able access data on screen by following code. My problem is that while Values changes in database it should be changed on the screen without any button press or something.
stream: FirebaseDatabase.instance.ref("app3-e0228-default-rtdb").onvalue,
while i use above line the data on the screen will not accessible. what should I do?
StreamBuilder(
stream: FirebaseDatabase.instance.ref("app3-e0228-default-rtdb").once().asStream(),
builder: (context,AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
DatabaseEvent databaseEvent = snapshot.data!; // 👈 Get the DatabaseEvent from the AsyncSnapshot
var databaseSnapshot = databaseEvent.snapshot; // 👈 Get the DataSnapshot from the DatabaseEvent
print('Snapshot: ${databaseSnapshot.value}');
return Text("${databaseSnapshot.value}");
} else {
return CircularProgressIndicator();
}
}),
DataBase
The first problem I spot is here:
stream: FirebaseDatabase.instance
.ref("app3-e0228-default-rtdb")
.once()
.asStream(),
You're calling once(), which means you read the value from the database once (hence its name), and then turn that into a stream. So your stream will just have one element.
If you want to listen to the database and receive updates too, use onValue:
stream: FirebaseDatabase.instance
.ref("app3-e0228-default-rtdb")
.onValue
Inside your builder you'll then have a DataSnapshot that contains the data from your database, and you can simply do:
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var databaseEvent = snapshot.data!; // 👈 Get the DatabaseEvent from the AsyncSnapshot
var databaseSnapshot = databaseEvent.snapshot; // 👈 Get the DataSnapshot from the DatabaseEvent
print('Snapshot: ${databaseSnapshot.value}');
return LiveMatch();
} else {
return CircularProgressIndicator();
}
Have you implemented streamSubscription and tried listening to it?
StreamBuilder will automatically update data whenever there are changes made.

How to display a Firebase list in REAL TIME?

I have a TODO List function (Alarmas), but I feel I'm not taking advantage of Firebase's Realtime features enough.
The Widget displays the list very well, however when someone puts a new task from another cell phone, I am not being able to show it automatically, but I must call the build again by clicking on the "TODO button" in the BottomNavigationBar.
Is there a way that the new tasks are automatically displayed without doing anything?
I'm using streams to get the list...
#override
Widget build(BuildContext context) {
alarmaBloc.cargarAlarmas();
///---Scaffold and others
return StreamBuilder(
stream: alarmaBloc.alarmasStream,
builder: (BuildContext context, AsyncSnapshot<List<AlarmaModel>> snapshot){
if (snapshot.hasData) {
final tareasList = snapshot.data;
if (tareasList.length == 0) return _imagenInicial(context);
return ListView(
children: [
for (var itemPendiente in tareasList)
_crearItem(context, alarmaBloc, itemPendiente),
//more widgets
],
);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center (child: Image(image: AssetImage('Preloader.gif'), height: 200.0,));
},
),
And, I read the Firebase Data in this way...
Future<List<AlarmaModel>> cargarAlarmas() async {
final List<AlarmaModel> alarmaList = new List();
Query resp = db.child('alarmas');
resp.onChildAdded.forEach((element) {
final temp = AlarmaModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idAlarma = element.snapshot.key;
alarmaList.add(temp); // element.snapshot.value.
});
await resp.once().then((snapshot) {
print("Total list was loaded - ${alarmaList.length}");
}); //I'm using this await to be sure that the full list was loaded, so I can order and process it later
return alarmaList;
}
How can I display a List from Firebase in "true" Real Time?
To properly manage the state of asynchronously loaded data, you should:
Start loading/listening to the data in initState()
Set the data into the state (with setState()) when you receive it or it is updated.
Then render it from the state in the build method.
So in your code that'd be something like:
final List<AlarmaModel> alarmaList = new List(); // this is now a field in the `State` object
#override
void initState() {
super.initState();
Query resp = db.child('alarmas');
resp.onChildAdded.forEach((element) {
final temp = AlarmaModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idAlarma = element.snapshot.key;
alarmaList.add(temp);
setState(() {
alarmaList = alarmaList;
})
});
}
#override
Widget build(BuildContext context) {
///---Scaffold and others
return StreamBuilder(
stream: alarmaBloc.alarmasStream,
builder: (BuildContext context, AsyncSnapshot<List<AlarmaModel>> snapshot){
if (snapshot.hasData) {
final tareasList = snapshot.data;
If you only want to repaint once you've gotten a complete update from the database, you can put the call to setState() in a value listener, just use onValue in that instead of once(), as you also want to catch the updates.

Why is my builder function executed twice?

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.

StreamBuilder returns nothing in the snapshot

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

Resources