Firestore changes are not synced with stream builder - flutter

When I update the database in console or app, the changes are not synced in the app. I thought it's because of orderBy and indexing settings, but commenting them didn't help. What's wrong with my code? How to set up auto-sync?
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: DrawerMenu(),
appBar: AppBar(
title: Text('Заявки'),
),
body: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection('orders')
.where('companyId', isEqualTo: globals.companyId)
// .orderBy('dateAdded', descending: true)
// .orderBy('statusId')
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return LoadingScreen();
default:
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) => GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Items(
order: snapshot.data.documents[index],
),
)),
child: OrderCard(
order: snapshot.data.documents[index],
)));
}
},
),
);
}

Its because you are just getting the snapshot as a stream once, but to get the actual changes when you modify in console or app you need to make a subscription to the stream. You need to .listen the stream of data so that you get notified when something change.
Also you may want to separate the logic of firebase its easier.
For example:
Create a Stream Controller:
final StreamController<DataType> _streamController = StreamController<DataType>();
Listen to firebase data and add it to stream:
Firestore.instance
.collection('orders')
.where('companyId', isEqualTo: globals.companyId)
.snapshots().listen((DocumentSnapshot snapShot){
_streamController.add(snapShot.documents);
});
Now you can listen to the stream from stream controller:
_streamController.stream;

It turned out that it's necessary to add custom indexes in firestore console when a query consists of .where().orderBy and so on.

Related

Modify Future Builder inside Stream Builder to avoid widget flickering

I am using a FutureBuilder inside a StreamBuilder that updates the UI every time a document is added to the activity collection, to get some aditional data from Firestore. The problem is that the FutureBuilder returns a SizedBox widget while the ConnectionState is waiting causing the all the cards to dissapear for a second. I would like to avoid this flickering since it causes a bad ui experience for users.
Is there a way to query the required user data in the activity stream so it all returns at once that way I can remove the FutureBuilder?
If not ... what would be a solution for this?
activityStream() {
return FirebaseFirestore.instance
.collection('activity')
.orderBy('timestamp', descending: true)
.limit(55)
.snapshots();
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox(
height: 65.0,
);
}
StreamBuilder<QuerySnapshot>(
stream: activityStream(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const Center(child: CircularProgressIndicator());
default:
final activityContent = snapshot.data?.docs
.map((data) => ActivityModel.fromFirestore(data))
.toList();
return Scrollbar(
controller: widget.scrollController,
child: ListView.builder(
shrinkWrap: true,
controller: widget.scrollController,
itemCount: activityContent!.length,
itemBuilder: (context, i) {
return FutureBuilder(
future: FirebaseFirestore.instance
.collection('users')
.where('uid', whereIn: activityContent[i].players)
.get(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox(
height: 65.0,
);
}
final users = snapshot.data!.docs.map((e) {
return UserModel.fromFirestore(e);
}).toList();
return MyWidget(
users: users,
);
},
);
},
),
);
}
},
);

How to refresh sort stream [Flutter]

Hello I'm building chat app with firestore database but I have a problem,
when I send a new message and copy it it will copy the above message (old message) and when I refresh page I can copy the new message normally
I know the problem is the stream sorted the messages but not the new messages, so how I refresh the stream every new message sent?
I can fix that problem with setState but I am using Getx.
Can anyone help me with that? or advice I want only refersh the stream.
when I remove descending from stream everything work fine but I need to sort messages.
** sorry code look weird because i shorted it too much
Stream<QuerySnapshot<Map<String, dynamic>>>? snap()=> _fireStore
.collection('chatRooms')
.doc(chatRoom.chatRoomId)
.collection('messages')
.orderBy('createdon', descending: true)
.snapshots();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: StreamBuilder(
stream: snap(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.hasData) {
QuerySnapshot dataSnapshot =
snapshot.data as QuerySnapshot;
return ListView.builder(
reverse: true,
itemCount: dataSnapshot.docs.length,
itemBuilder: (context, index) {
MessageModel currentMessage =
MessageModel.fromMap(dataSnapshot.docs[index]
.data() as Map<String, dynamic>);
return Column(
children: [
GestureDetector(
child: Text('${currentMessage.text}'),
onLongPress: () {
showModalBottomSheet(
context: context,
builder: (_) {
return Wrap(
children: [
ListTile(
title: Text(
'Copy message'),
leading: Icon(
Icons.copy),
onTap: () {
Clipboard.setData(
new ClipboardData(
text: currentMessage.text));
Get.back();
print('Copied');
},
)
],
});
} else if (snapshot.hasError) {
....
You can try this.
QuerySnapshot dataSnapshot = snapshot.data as QuerySnapshot;
final sortedMessages= dataSnapshot.docs.sort((a, b)=> a['createdon'].compareTo(b['createdon']));
And in item builder,
itemBuilder: (context, index) {
MessageModel currentMessage =
MessageModel.fromMap(list[sortedMessages]
.data() as Map<String, dynamic>);//Create item from new list

Continuously Retrieving data from stream builder

I am trying to implement stream builder with cloud firestore to retrieve field data.
Here is the code:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("test builder"),
),
body: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('joystick').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
if(!snapshot.hasData){
return Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(
itemCount: snapshot.data?.docs.length,
itemBuilder: (context, i){
QueryDocumentSnapshot<Object?>? ds = snapshot.data?.docs[i];
return Text("$snapshot.data?.docs[i].data()!['call']");
});
}
),
);
}
However, it does not output the actual data stored in the database.
I get the following output:
AsyncSnapshot<QuerySnapshot<Object?
>>(ConnectionState.active,Instance of '_JsonQuerySnapshot',null, null).data?.docs[i].data()!['call']
What should I do to get the data stored in the database? (The field name is 'call')
In order to implement stream builder with cloud firestore to retrieve field data, you can follow the StreamBuilder Documentation and link related to explanation of streambuilder in flutter.
As mentioned the thread, you can go through the following code on retrieving data through streambuilder :
body: new StreamBuilder(
stream: Firestore.instance.collection("collection").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text( 'No Data...', );
}
else {
<DocumentSnapshot> items = snapshot.data.documents;
return new Lost_Card(
headImageAssetPath : items[0]["url"] );
}
If you want to create list builder from many documents use it like
this
return new ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot ds = snapshot.data.documents[index];
return new Lost_Card(
headImageAssetPath : ds["url"];
);
For more information, you can refer to the Documentation where real time updates with cloud firestore have been explained.
Try this one to return your data
return Text(ds['enterYourDataFieldName']);
What you are trying to achieve concerns RealTime database, Firestore database is not made for that.

Update ListView.builder itemCount when firestore document is added

I have a flutter app where a list is generated with ListView.Builder, and where the itemCount is the number of documents in a firestore collection. This works fine until a new document is added. When that happens I get the error (17 and 18 are just examples).
Invalid value: Not in range 0..17, inclusive: 18
I assume I would need to update the state when a new document is created, but I have no idea how i can call setState when that happens
Here is the relevant part of the code:
child: StreamBuilder(
stream: Firestore.instance.collection('contact').orderBy(sortby, descending: decending).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Container();
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) =>
_personer(context, snapshot.data.documents[index], index),
);
},
),
use setState?
StreamBuilder(builder: (context, snapshot) {
return snapshot.hasData == null ? Container() : _getListView(snapshot);
} , )
_getListView(snapshot) {
setState(() {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) =>
_personer(context, snapshot.data.documents[index], index),
);
});
}
StreamBuilder use QuerySnapshot so list data can change
example code :
StreamBuilder<QuerySnapshot>(
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting: return new Text('Loading...');
default:
return new ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
return ;
}).toList(),
);
}
},
)

Is nested StreamBuilder good pattern?

I am fetching articles from HackerNews API using Bloc Pattern and Streams.
I am loading all the articles and presenting in the UI with the help of a stream builder, and this works fine.
Now I wrapped the article fetching Stream builder with the new loading StreamBuilder.
Now when the loading stream builder has true (means it is loading) it shows a circular progress indicator or else, it shows the child (Article List wrapped with a Stream Builder).
This works fine. But it is bugging me that I have wrapped Stream builder inside a stream builder. I know I can take help of rxdart but I am just not sure how.
I tried to add a loader with the help of snapshot.hasData or not but that didn't work, so I decided to create another stream and subject that takes a bool and tells the UI if it is loading or not.
Code fetching data int the bloc:
_getAndUpdateArticles(StoryType storyType) {
_isLoadingSubject.add(true);
getListIds(storyType).then((list) {
getArticles(list.sublist(0, 10)).then((_){
_articleSubject.add(UnmodifiableListView(_articles));
_isLoadingSubject.add(false);
});
});
}
UI:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: StreamBuilder(
stream: widget.hnBloc.isLoading,
builder: (context, snapshot) {
if (snapshot.data) {
return Center(child: CircularProgressIndicator());
} else {
return StreamBuilder<UnmodifiableListView<Article>> (
initialData: UnmodifiableListView<Article>([]),
stream: widget.hnBloc.article,
builder: (context, snapshot) => ListView(
children: snapshot.data.map(_buildItem).toList(),
),
);
}
},
),
.........
EDIT
I have tried this, but this isn't working:
StreamBuilder<UnmodifiableListView<Article>> (
initialData: UnmodifiableListView<Article>([]),
stream: widget.hnBloc.article,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView(
children: snapshot.data.map(_buildItem).toList(),
);
} else {
return CircularProgressIndicator();
}
}
),
I Don't think there is a complete way to avoid nested StreamBuilders. I personally wouldn't consider it a bad practice, but it will definitely lead to more build.
In your case, You can modify your hnBloc to emit a single state that can be a loading state or data state , thereby eliminating the need for a nested StreamBuider.
eg.
StreamBuilder<HnState>(
stream: hnBloc.currentState,
initialData: HnLoadingState(),
builder: (context, snapshot) {
if (snapshot.data is HnLoadingState) {
return Center(child: CircularProgressIndicator());
}if (snapshot.data is HnDataState) {
return ListView(
children: snapshot.data.map(_buildItem).toList(),
),
}
},
)
This pattern is very common when using the flutter_bloc package. You can see a basic example of this here to understand it better.