I am building a one to one chat app using Flutter and firebase and I want to display all the chats under label of the day on which it happened,like it is on all major chat apps.
I retrieve data from firestore in ascending order of time by using order by command on the timestamp field of each message and as suggested by NiklasLehnfeld
i used groupBy method in the collection package to group my messages and used nested list view builders outer one for creating groups and inner one for filling those groups with data.
Here is the code
Widget build(BuildContext context) {
return FutureBuilder(
future: FirebaseAuth.instance.currentUser(),
builder: (ctx, futureSnapshot) {
if (futureSnapshot.connectionState == ConnectionState.waiting) return Center(child: CupertinoActivityIndicator());
return StreamBuilder(
stream: Firestore.instance
.collection('mychats/${futureSnapshot.data.uid}/${widget.recieverUid}')
.orderBy('createdAt', descending: true)
.snapshots(),
builder: (context, chatSnapshots) {
if (chatSnapshots.connectionState == ConnectionState.waiting)
return Center(child: CupertinoActivityIndicator());
else {
final List chatDocs = chatSnapshots.data.documents;
Map<String, List> grouped = groupBy(chatDocs, (chat) {
String dateTime = chat['dateTime'];
return dateTime;
});
(grouped.forEach((m, v) {
print('$m');
for (int i = 0; i < v.length; i++) {
print(v[i]['text']);
}
}));
return ListView.builder(
reverse: true,
itemCount: grouped.keys.length,
itemBuilder: (ctx, index1) {
String date = grouped.keys.toList()[index1];
List messages = grouped[date];
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(date),
ListView.builder(
reverse: true,
primary: false,
shrinkWrap: true,
itemCount: messages.length,
itemBuilder: (context, index) {
return MyMessageBubble(
chatDocs[index].documentID,
chatDocs[index]['text'],
(chatDocs[index]['userId'] == futureSnapshot.data.uid) ? true : false,
ValueKey(chatDocs[index].documentID));
})
],
);
});
}
},
);
},
);
}
This is the list of messages that I am fetching
This is the UI .The results that i am printing in console are not displaying correctly in UI.I tried setting keys for both the list builders but no success. Is there any way it can be corrected
You are using the wrong list:
This:
chatDocs[index].documentID,
chatDocs[index]['text'],
(chatDocs[index]['userId'] == futureSnapshot.data.uid) ? true : false,
ValueKey(chatDocs[index].documentID));
should reference messages, not chatDocs. Because index is the index into messages.
the documentId is not work
chatDocs[index]['text'],
chatDocs[index]['userId'] == futureSnapshot.data.uid,
key: ValueKey(chatDocs[index].id),
just use only id
Related
I have already get the snapShot of array and count it using length. And displayed it. But I want to reorder it from much to lower in a listview.builder. How can I achieve that?
Backed structure
Code so far
//I forgot to mention
//This is the usersCommentId from the snapShot of StreamBuilder on top of the widget tree
final usersComment = snapshot.data?['usersComment'];
ListView.builder(
physics: const BouncingScrollPhysics(),
itemCount: usersComment.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection("usersComment")
//I tried filtered it here But that doesn’t work
.where(usersComment[index])
.snapshots(),
builder: (context,
AsyncSnapshot<
QuerySnapshot<
Map<String, dynamic>>>
snapshot) {
final userComments = snapshot.data!.docs
..sort((a, b) => ((b.data()['vote']
as List<dynamic>?)
?.length ??
0)
.compareTo((a.data()['vote']
as List<dynamic>?)
?.length ??
0));
final comment = userComments[index];
final countVote = (comment.data()['vote']
as List<dynamic>?)
?.length ??
0;
if (!snapshot.hasData) {
return Container();
}
return Text(
countVote.toString(),
style: const TextStyle(
color: Colors.black, fontSize: 15),
);
});
}),
As you have already taken all List of userComments, I have a suggestion to make it with in single Stream query as following
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("usersComment").snapshots(),
builder: (context,
AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>>
snapshot) {
final userComments = snapshot.data!.docs..sort((a, b) => ((b.data()['vote'] as List<String>?)?.length ?? 0).compareTo((a.data()['vote'] as List<String>?)?.length?? 0));
ListView.builder(
itemCount: userComments.length,
itemBuilder: (context, index){
final comment = userComments[index];
final countVote = (comment.data()['vote'] as List<String>?)?.length ?? 0;
return Text(
countVote.toString(),
style: const TextStyle(
color: Colors.grey,
fontSize: 9),
);
});
});
If you want to filter the userComments, then stream: FirebaseFirestore.instance .collection("usersComment").where(%your condition%).snapshots()
take out the listview.
return listView inside the stream builder.
return everything in the collection (not each doc).
add all the votes in one list inside your stream builder.
sort the list and display using the ListView that is inside the stream
how to sort in dart
You can sort the usersComment array before passing it to the ListView.builder by using the sort method from the List class. You can sort the list by using a custom sorting function that compares the vote counts of each DocumentSnapshot in the list. Here is an example:
usersComment.sort((a, b) {
return FirebaseFirestore.instance
.collection("usersComment")
.doc(b)
.get()
.then((bSnapshot) {
final bVote = bSnapshot.data?['vote'];
final bCountVote = bVote?.length ?? 0;
return FirebaseFirestore.instance
.collection("usersComment")
.doc(a)
.get()
.then((aSnapshot) {
final aVote = aSnapshot.data?['vote'];
final aCountVote = aVote?.length ?? 0;
return bCountVote - aCountVote;
});
});
});
here is my database first Image
2nd Image
finlal Image
whenever user uploads story the "stroyAdded" feild gets true thats how Iam fetching the list of the user who have uploaded the story
here is the code of the particular
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection("users")
.where("stroyAdded", isEqualTo: true)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.connectionState ==
ConnectionState.active) {
return ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
var data =FirebaseFirestore.instance.
collection("users").doc(snapshot.data!.docs[index].
get("uid")).collection("Stories").snapshots();
print(snapshot.data!.docs.length);
return Padding(
padding: EdgeInsets.symmetric(
horizontal: 10),
child: StoryCircle(
() {
Navigator.push(context, MaterialPageRoute(builder: (context)=>StoryPage();
},
snapshot.data!.docs[index]
.get("profile"),
snapshot.data!.docs.length),
);
});
}
} else {
// No data
}
return const Center(
child: CircularProgressIndicator());
})
My desire output
I'm guessing that you only want to show the storys from the last 24h like other solical media plattforms. In this case the best way to query all the stories from the diffrent collections would be to us a singel query with a collection group query.
You can read more about it here: https://firebase.google.com/docs/firestore/query-data/queries#java_30
My solution:
db.collectionGroup("Stories").whereGreaterThan("time", yourDateObject - 24h)
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
// ...
}
});
i am creating a app in which i need to show total price of the cart items:
so i am getting data in json and returning that data in futurebuilder and accesing it like;
list[index]['price']
and intialize a variable for total :
double totalPrice = 0;
and adding values as follows:
FutureBuilder(
future: myFuture,
builder: (context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
// still waiting for data to come
return Center(
child: CircularProgressIndicator(),
);
} else if (snapshot.hasData &&
snapshot.data.isEmpty) {
return Center(child: Text("No Products"));
;
} else {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, index) {
//below the code of adding price
List list = snapshot.data;
totalPrice = totalPrice + list[index]['price'];
print("this is the total:$totalPrice");
return Container(..My Ui..
):Center(child: Text("no data"));
}
}, //futurebuilder ends here
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Total :₹ $totalPrice', // showing total price here
style: TextStyle(
fontSize: 20,
color: darkGrey,
fontWeight: FontWeight.bold),
),
),
but this totalPrice always show 0
can someone tell me how to update this variable inside a build?
Thanks in advance <3
Not try to do like this. Use these method..
FutureBuilder(builder: (context, snapshot) {
if(snapshot.hasData){
//Only run when you get data
final list = snapshot.data;
var totalPrice = list.map((e) => e[price]).toList()
.reduce((value, element) => value + element);
return ReturnYourWidget();
}
//Until you get data show the loader
return Center(child: CircularProgressIndicator(),);
},);
I am assuming your totalPrice is in a stateful widget?
I would make a function that takes the snapshot data and calculates the total price. After that updating the totalPrice. This allows you to keep the ListviewBuilder as it is.
Most importantly, don't forget to use setState(() => {totalPrice = <calculated>});. This notifies the widget of the change.
Additionally, this ensures your totalPrice is accurate since the ListBuilder only builds items that are currently being rendered on the screen.
if (snapshot.hasData) {
final list = snapshot.data;
var totalPrice = list.map((e) => e[price]).toList()
.reduce((value, element) => value + element));
}
Hi I have a simple query for Firestore in a StreamBuilder
StreamBuilder(
stream: FirestoreManager.firebaseFirestore
.collection("orders")
.orderBy('logs.0', descending: true)
.where('status', whereIn: current['id'])
.snapshots(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snap) {
print(snap.data.toString());
if (!snap.hasError && snap.hasData) {
QuerySnapshot snapshot = snap.data;
if (snapshot.documents.isNotEmpty) {
List<DocumentSnapshot> snapList = snapshot.documents;
return ListView.builder(
padding: EdgeInsets.only(right: 10, left: 10),
physics: ScrollPhysics(),
itemCount: snapList.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return OrderListItem(
order: Order.fromJson(snapList[index].data),
);
},
);
} else {
return Center(
child: Text(
"No ${current['status'].toString().trim()} Order Available...!",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
);
}
} else {
return Center(
child: CircularProgressIndicator(),
);
}
})
It works absolutely fine for mobile appilcaiton but when I try to run it for web, It doesn't work.
Actually it shows data once for a second and again disappears. I got following log in console by printing snapshot data using print(snap.data.toString());
js_primitives.dart:30 null
js_primitives.dart:30 Instance of 'QuerySnapshot'
js_primitives.dart:30 null
Why is this happening? Why it shows data once and again disappear it?
If I remove either .orderBy('logs.0', descending: true) or .where('status', whereIn: current['id']) then it works fine.
I am having the same problem with flutter web. I am trying to use two chained where() methods and it doesn't give me any results:
StreamBuilder(
stream: fire
.collection('thiHistory')
.where("device", "==", _device)
.where("ts", '>=', ts)
.onSnapshot,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return Text('Loading....');
final docs = snapshot.data.docs;
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: docs.length,
itemBuilder: (context, index) {
final doc = docs[index].data();
return doc["device"] == _device ? Column(
children: [
Text(doc['device']),
Text("${doc["hour"]}"),
Text("${doc["temp"]}"),
Text("${doc["humi"]}"),
Text("${doc["thi"]}"),
],
) : Container();
});
},
)
If I only use one where() or the other it works fine. If I use both, I always get "Loading....", which tells me the snapshot has no data.
I also tried chaining orderedBy() with where() like Shahzad Akram, and same thing... Using one or the other works great, combining them does not return data.
I am using the firebase 7.3.0 package and version 7.19.1 of the libraries:
<script src="https://www.gstatic.com/firebasejs/7.19.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.19.1/firebase-database.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.19.1/firebase-firestore.js"></script>
Thanks for your help.
I want to display a special widget with listView.builder every fourth item.
I got these two lists
List<Event> events = [];
List<SpecialEvent> specialEvent = [];
These lists get filled by two Streambuilder like so
StreamBuilder(
stream: stream,
builder: (BuildContext context,
AsyncSnapshot<List<DocumentSnapshot>> snapshot) {
if (!snapshot.hasData) {
//Spinner
}
events = snapshot.data.map((doc) => Event.fromDocument(doc)).toList();
if (events.isEmpty) {
//Code here
}
return StreamBuilder(
stream: stream,
builder: (BuildContext context,
AsyncSnapshot<List<DocumentSnapshot>> snapshot) {
if (!snapshot.hasData) {
//Spinner
}
specialEvents = snapshot.data
.map((doc) => specialEvents.fromDocument(doc))
.toList();
eventCount = 0;
specialEventCount = 0;
return Flexible(
child: ListView.builder(
itemCount: events.length + specialEvents.length,
itemBuilder: (context, index) {
int newIndex = index + 1;
if (index != 0 &&
newIndex % 4 == 0 &&
specialEventCount.length > specialEventCount ||
specialEventCount.length > specialEventCount &&
events.length <= eventCount) {
specialEventCount += 1;
return buildSpecialEvent(specialEventCount - 1);
} else if (events.length > eventCount) {
eventCount += 1;
return buildEvent(eventCount - 1);
} else {
return Text("");
}
},
),
);
});
},
);
This code works really well when it comes to the initial list view build.
Every fourth item is a specialEvent.
The issue:
The app user can tap on each of these events/specialEvents and gets navigated to a different screen. There the user can do some stuff and change data inside the database.
This leads to a rebuild of the list view.
Depending on the position inside of the list view the builder does not start to rebuild the widget with index 0. That in turn leads to a chaos with the evenCount and the specialEventcount and everything gets mixed up.
Does anybody know how to proper solve this issue?
I guess there has to be a better solution to build a special widget every fourth item than mine.
Instead of ListView.builder, use ListView.separated,
hear is an example snippet
ListView.separated(
itemCount: 40,
itemBuilder: (context, index) {
return ListTile(
title: Text('$index'),
);
},
separatorBuilder: (context, index) {
return index == 0
? SizedBox.shrink()
: index % 4 == 0
? Container(
height: 20,
color: Colors.red,
)
: SizedBox.shrink();
},
),