Streambuilder is not updating in Flutter - firebase

I am using Streambuilder and loading Snapshot from firebase. After loading the snapshot, I am putting my data into Post.dart where I made widget structure. My code can get the data, but when I delete the one of the post from the firebase, it still show the same posts, but last one disappears instead of the deleted one. However, if I change my page and come back, right one is deleted and everything is fine. So I think flutter knows that I am changing my firebase, but does not know how to map it into my Post. Any thought?
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("timeline")
.doc(widget.currentUser.id)
.collection('timelinePosts')
.orderBy('timestamp', descending: true)
.snapshots()
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> streamSnapshot) {
var items = streamSnapshot.data != null &&
streamSnapshot.data.docs != null
? streamSnapshot.data.docs
: [];
List<Post> posts =
items.map((doc) => Post.fromDocument(doc)).toList();
return !streamSnapshot.hasData ||
streamSnapshot.connectionState ==
ConnectionState.waiting
? Center(
child: CircularProgressIndicator(),
)
: Column(
children: posts);
})
and Post is something like
#override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
...
],
);
}

You need to provide a unique key to your post item widgets so that when flutter rebuilds the column, it is able to differentiate between the old posts and the new list of posts. Basically it has to do with how flutter decides to rebuild certain elements and when.
If you want to test whether a unique key can solve the problem I usually start by assigning a key like this to the post widgets:
key: Key("${Random().nextDouble()}"),
And seeing if that changes anything. If it fixes it, you can try a more efficient key like a combination of the properties of each element.

I might be a bit late to drop my 2 cents, but you can try to add this code to your Post Widget:
#override
void didUpdateWidget(Post oldWidget) {
//add the code you want it to update inside your Post widget.
super.didUpdateWidget(oldWidget);
}

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

Flutter Listview duplicates random element instead of adding new element on new data

I'am trying to create something like feed with Flutter and Firebase and I want to order posts by their 'postedAt' value but the problem is when I use collection.orderBy() it is not working properly. It adds new post to the random place at the feed and gives it a imageURL of random existing post (or I think it just duplicates one item in the feed). I created indexes at Firebase I tried both index types none working. The strange thing is, when I'm adding new post from the phone when my adding completed it just works fine but when listing on the other phone it shows described behaviour.
Edit: when I hot reload it works correct (last added post shows up on the top)
Edit: when I removed the orderBy method problem continued.
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("users")
.doc(FirebaseAuth.instance.currentUser.uid)
.collection("posts")
.orderBy('postedAt', descending:true)
.snapshots(),
builder: (BuildContext context,AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
print("error:" + snapshot.error.toString());
}
List<Post> posts = [];
posts = snapshot.data.docs.map((postDoc) {
return Post.fromDoc(postDoc);
}).toList();
if (posts.isEmpty) {
return Center(
child: Text("No posts"),
);
}
return ListView.builder(
cacheExtent: 4000,
itemCount: posts.length,
itemBuilder: (BuildContext context, int index) {
return PostWidget(
post: posts[index],
);
},
);
},
)
my indexes:
Composite index
single index
screenshot of the document:
document ss
I solved this issue.
I thought that if it is duplicating the elements, may be it cannot seperate them. and added key paramater to PostWidget so it worked.

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.

Flutter using StreamBuilder with setState updates

I'm using a StreamBuilder inside a build method to retrieve user info from firestore and right after it is displayed with a Listview.builder.
Once i have retrieved the user info, i call another API with the user id's to retrieve some other info i would like to display in the list. This gives me a Future for each user, once the future has been "fulfilled" (inside Future.then()), then i save the data and call SetState(); to rebuild the list with the new data.
The problem is once setState() is called the Build method is rerun which gives me the users again, and this causes an endless loop of retrieving the users and the data from the second API.
This seems like a typical scenario but i don't know how to solve this with StreamBuilder. My previous solution retrieved the users in initState so it was not recalled endlessly.
Any tips?
"getOtherAPIData" loops the users retrieved and adds data "distance" to the list, and the list is sorted based on the user with the most/least distance.
Code:
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<List<IsFriend>>(
stream: viewModel.isFriendStream(),
builder: (_, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
List<IsFriend> friends = snapshot.data;
if (friends == null || friends.isEmpty) {
return Scaffold(
body: Center(
child: Text("Friend list is empty :("),
)
);
} else {
userList = friends;
getOtherAPIData();
return getListview(userList);
}
}
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
)
);
}
),
..
..
void getOtherAPIData() async {
for (IsFriend friend in userList) {
await fitbitServ.getData(friend.user.uid).then((fitData) {
setState(() {
setDistanceOnUser(fitData.distanceKm, friend.user.uid);
totalDistance += fitData.distanceKm;
sortUsers(userDataList);
userData[friend.user.uid] = fitData;
});
});
}
}
You should never call a method that starts loading data from within your build method. The normal flow I use is:
Start loading the data in the constructor of the widget.
Call setState when the data is available.
Render the data from the state in build.

Flutter : How to add a listener that will do something whenever the data is changed in Cloud Firestore Database?

I'm a newbie in Flutter and Cloud Firestore. So far I've got hold of using basic 'get' and 'post' requests. I'm using cloud firestore in my flutter project currently and my goal is to auto refresh the page whenever a certain document or collection data is changed. Hence I was curious about how can I add a listener to the database which will notify the app whenever there is a change in the database. Thanks in advance for answering.
I followed the answer provided by #Frank Van Puffelen (One with a green tick) and it worked perfectly.
Note :
Before doing this make sure you have a proper internet connection.
If you are using a virtual device (Android Emulator), it might not work properly.Instead as soon as you update the database, it will give an error like this :
" [{0}] Failed to resolve name. status={1} "
In this case its the issue of the virtual device and not your code. You need to restart your virtual device to see the changes or you can test your code on a physical device where it will reflect immediately as soon as you make changes in the database.
The cloud_firestore plugin package has a great example of how to use it, which shows precisely how to do this.
The relevant code for what you're asking:
final Firestore firestore;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: firestore
.collection("messages")
.orderBy("created_at", descending: true)
.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];
final dynamic message = document['message'];
return ListTile(
trailing: IconButton(
onPressed: () => document.reference.delete(),
icon: Icon(Icons.delete),
),
title: Text(
message != null ? message.toString() : '<No message retrieved>',
),
subtitle: Text('Message ${index + 1} of $messageCount'),
);
},
);
},
);
}

Resources