Flutter - ReOrder Data Stored in FireStore - flutter

As you can see, I am dragging Tasks by using ReorderableListView widget of Flutter.
The onReorder is able to drag the tasks up and down. However, when I close the app, all of the tasks go in the default order as it was arranged.
This is because my data which is being fetched from the CloudFireStore isn't changing its order, the way is it updated in my dragging.
Can anyone help me, how can I update the position of tasks stored as documents in Cloud FireStore, so that when I close the app and open it again, it shows new updated positions and not old positions of the task
The code :
child: StreamBuilder(
stream: FirebaseFirestore.instance
.collection("Tasks")
.doc(_email)
.collection("User_Tasks_List")
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text("Loading!...");
_docs = snapshot.data.documents;
return Theme(
data: ThemeData(canvasColor: Colors.transparent),
child: ReorderableListView(
children: _docs
.map((e) => InkWell(
key: ObjectKey(e),
onTap: () => _popupDialog(context, e),
onDoubleTap: () => FirebaseFirestore
.instance
.runTransaction(
(transaction) async {
transaction.delete(e.reference);
Fluttertoast.showToast(
msg: "Task has been deleted!");
}),
child: ViewHolder(e)))
.toList(),
onReorder: onReorder),
);
}),

You can add a parameter like index:x to items in firestore. Then you need to save the order of the ordered list items and store them in Firestore using the indexes.
When you fetch items you need to sort them by this index and you will get the same order.
objects.sort((a, b) => a.index.compareTo(b.index));

Related

Need I do pagination for flutter StreamBuilder?

I'm new to flutter, and now I'm creating an app which has a feed page, I'm using StreamBuilder + firestore to do this, the code is like this:
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('posts')
.orderBy('createdAt', descending: true)
.snapshots(),
builder: (context,
AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(
color: primaryColor,
),
);
}
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) => Container(
child: createPostViewFromSnapShot(snapshot.data!.docs[index]),// it returns a widget
),
);
},
);
You can see from the code I didn't do pagination, I want to know when the code runs, it fetch all the post from firestore ? Or it will fetch data by block or something like pagination ?
I want to know if it's necessary to do pagination for StreamBuilder?
Avoid creating Widgets in Methods. That way you cause your App to
become I'm-performant. Because the Widget is not sat directly in the
Tree, but a method, every build that methods gets called, no matter
if the resulting widget would have to be actually be rebuilt.
That stream will not always emit events where data is not null. You will most likely get an exception for using snapshot.data! bang operator
Take a closer look at the documentation: https://firebase.flutter.dev/docs/firestore/usage/
FlutterFire provides support for dealing with realtime 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).
For what you are trying to achieve, it would be better to initially fetch a limited set of documents from your collection.
collectionReference.limit(42).get()
You can than fetch the next elements by using https://firebase.flutter.dev/docs/firestore/usage/#start--end-cursors

Flutter + Firestore chat .. Listview rebuilds all items

I'm trying to add chat functionality to my app (kind of similar to WhatsApp functionalities), using flutter and Firestore. The main structure on Firestore is to have 2 collections (I want the unread message count as well):
users: and each user will have a subcollection "chats" that will include all CHATS_IDs. This will be the main place to build home chat page (shows a history list of all chats) by getting the user chat list.
chats: a list of all chats and each chat document has a subcollection of messages.
My main issue is in building the home page (where a list of all user previous chats should appear). I get/subscribe the user chat subcollection, and for each chat ID listed in there I also subscribe for the chat itself in the chat collection (using the ID).
Here are screenshots of it in principle:
users collection:
chats coleection:
and here is the main screen of interest (principle from whatsapp screen):
What I'm doing is that I retrieve user's chat subcollection (and register a listener to it using StreamBuilder), and also for number of unread messages/last message and last message time, I subscribe to listen for each of these chats (and want to use each user last message time, status and his last presence in that chat doc to calculate the unread count) .
The problem is that Listview.builder rebuilds all items (initially and on scroll) instead of just the viewed ones. here is my code:
Stream<QuerySnapshot> getCurrentUserChats(userId) {
return FirebaseFirestore.instance
.collection(AppConstants.USERS_COLLECTION)
.doc('$userId')
.collection(AppConstants.USER_CHATS_SUBCOLLECTION)
.orderBy('lastMsgTS', descending: true)
.snapshots()
.distinct();
}
Widget getRecentChats(userId) {
return StreamBuilder<QuerySnapshot>(
stream: getCurrentUserChats(userId),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data.docs.isNotEmpty) {
print('snapshot of user chats subcoll has changed');
List<QueryDocumentSnapshot> retrievedDocs = snapshot.data.docs;
return Container(
height: 400,
child: ListView.builder(
//childrenDelegate: SliverChildBuilderDelegate(
itemCount: snapshot.data.size,
itemBuilder: (context, index) {
String chatId = retrievedDocs[index].id;
print('building index: $index, chatId: $chatId');
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection(AppConstants.CHATS_COLLECTION)
.doc('$chatId')
.snapshots()
.distinct(),
builder:
(context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasData) {
print('${snapshot.data?.id}, isExist: ${snapshot.data?.exists}');
if (snapshot.data.exists) {
return KeyProxy(
key: ValueKey(chatId),
child: ListTile(
leading: CircleAvatar(
child: Container(
//to be replaced with user image
color: Colors.red,
),
),
title: Text('$chatId'),
subtitle: Text(
"Last Message received on: ${DateTimeUtils.getDateViewFromDT(snapshot.data.data()['ts']?.toDate())}"),
),
);
}
}
return SizedBox.shrink();
},
);
},
/*childCount: snapshot.data.size,
findChildIndexCallback: (Key key) {
print('calling findChildIndexCallback');
final ValueKey valKey = key;
final String docId = valKey.value;
int idx = retrievedDocs.indexOf(retrievedDocs
.where((element) => element.id == docId)
.toList()[0]);
print('docId: $docId, idx: $idx');
return idx;
}*/
),
);
}
return Center(child: UIWidgetUtils.loader());
});
}
After searching, I found these related suggestions (but both didn't work):
A github issue suggested thesince the stream is reordarable (github: [https://github.com/flutter/flutter/issues/58917]), but even with using ListView.custom with a delegate and a findChildIndexCallback, the same problem remained.
to use distinct.
But removing the inner streambuilder and just returning the tiles without a subscription, makes the ListView.builder work as expected (only builds the viewed ones). So my questions are:
Why having nested stream builders causing all items to be rebuil.
is there a better structure to implement the above features (all chats with unread count and last message/time in real-time). Especially that I haven't added lazy loading yet. And also with this design, I have to update multiple documents for each message (in chats collection, and each user's subcollection).
Your help will be much appreciated (I have checked some other SO threads and medium articles, but couldn't find one that combines these features in one place with and preferably with optimized design for scalability/price using Firestore and Flutter).
I think that You can do this:
Widget build(ctx) {
return ListView.builder(
itemCount: snapshot.data.size,
itemBuilder: (index, ctx) =>_catche[index],
)
}
and for _catche:
List<Widget> _catche = [/*...*/];
// initialize on load

Two StreamBuilderon on one screen with default empty screen shown when neither has data

I'm trying to create a Split View with two ListViews, one of them showing Tasks which are not completed and the second one containing completed tasks. I managed to make this work with a Column containing two Streambuilder. The problem I don't know how to solve is how to show a single default empty screen when neither of the two Streams have any values, due to the way Flutter works, I can't just return null. So I end up having two Empty default screens in my Column and on my screen.
What would be the right approach to make something like shown in the GIF with Firestore?
If I need to work with a single Stream and filter it inside dart, how can I make it work with ListView ?
I'd highly appreciate an example.
My Job class contains a boolean value jobDone which tells me in which list the Job has to go.
My current code:
return Column(
children: [
StreamBuilder<List<Job>>(
stream: getPendingJobsStream(database),
builder: (context, snapshot) {
return ListItemsBuilder<Job>(
...
);
},
),
ExpansionTile(
title: Text('Completed Tasks'),
children: [
StreamBuilder<List<Job>>(
stream: getCompletedJobsStream(database),
builder: (context, snapshot) {
return ListItemsBuilder<Job>(
...
);
},
),
],
),
],
);
You can check for snapshot state
builder: (context, snapshot) {
if(snapshot.connectionState ==ConnectionState.waiting){
return Center(child:CircularProgressIndicator();
}
if(snapshot.connectionState ==ConnectionState.done){
//check if snapshot has data
if(snapshot.hasData){
return ListItemsBuilder<Job>( ..

how to display properly single value from a StreamBuilder 'Cloud Firestore'

i have data stored in filds like so => image
i have followed the documentation, the data just refuses to show for some reason.
it looks somthing like this
StreamBuilder(
stream:
FirebaseFirestore.instance.collection('users').snapshots(),
builder: (context, snapshot) {....}
)
and then
if (snapshot.connectionState == ConnectionState.active) {
print(snapshot.data.documents[0]['User Email']);
....}
note: the stream works fine but im assuming this happening coz of the way im calling it the snapshot.data.documents
how can i properly call them ?
I think it is because of the [0].
When you read just one of the documents, refer to:
FirebaseFirestore.instance.collection('users').doc('yourDocument').snapshots();
and if you will read more then one you can use this:
CollectionReference users = FirebaseFirestore.instance.collection('users');
return new ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document.data()['full_name']),
subtitle: new Text(document.data()['company']),
);
}).toList(),
);

next Page stream is not update on Flutter

I used two pages. and I added StreamBuilder in my first page and I passed snapshot.data to next Page. but when value change in 2nd-page value is not changing. How to fix it? I can't call streamBuilder in both pages because it's meaning two read in firebase. Is there any way to create singleton for services and access anywhere?
StreamBuilder(
stream: db.getData(),
builder: (context,snapshot){
return Column(
children: <Widget>[
InkWell(
onTap: ()=> Navigator.pushNamed(context, '/nextPage',arguments: Arguments(
data: snapshot.data
)),
)
],
);
},
)
InkWell(
onTap: ()=> Navigator.pushNamed(context, '/nextPage',arguments: Arguments(
data: snapshot.data
),
),
When using the above code, a data snapshot is only sent when you Tap on the InkWell. Meaning unless tapped on the inkwell it will not provide new data to nextPage.
To resolve this issue, I would suggest the following:
In First page
Create ValueNotifier instance to observe changes in the common reference:
// change Cast to type that you receive from firebase, or you can omit the cast
final ValueNotifier<snapshot_data_type> firebaseDataNotifier = ValueNotifier<snapshot_data_type>();
Update the value of firebaseDataNotifier when you receive data from firebase:
StreamBuilder(
stream: db.getData(),
builder: (context,snapshot){
firebaseDataNotifier.value = snapshot.data;
...
Pass the firebaseDataNotifier as data for the nextPage
In the next Page
Wrap the Next page widgets with ValueListenable
ValueListenableBuilder(
valueListenable: valueNotifier,
builder: (context, firebaseData, child) {
// return your next page widget here and use firebaseData instead of snapshot.data
)
Note: Make sure you keep the ValueNotifier instance outside of both widgets so that they can access it without any dependency.