Flutter asyncMap not run until setState - firebase

I am making a chat app that displays both a Group Chat and Private Chat in the same List.
I use Firestore as the database and store the data of User, Group and Contact in there. I have a Message Screen that displays a list of Chats that the User has using StreamBuilder.
I want to display data differently depending on the group's data. The group chat has their Group picture, Private Chat with User in Contact, their avatar display, and Private Chat with a generic icon display with User not in Contact.
I iterate through the stream first in a DatabaseService class, then put it in a variable and set it as a stream for StreamBuilder. This works fine, but I also want a list to check if a user already has a private chat with another User without getting the data from Firestore.
API.dart
//this is where I put my code to connect and read/write data from Firestore
final FirebaseFirestore _db = FirebaseFirestore.instance;
Api();
....
Stream<QuerySnapshot> streamCollectionByArrayAny(
String path, String field, dynamic condition) {
return _db
.collection(path)
.where(field, arrayContainsAny: condition)
.snapshots();
}
DatabaseService.dart
...
List<GroupModel> groups; //List of Groups
Stream<List<GroupModel>> groupStream; //Stream of List Group
...
Stream<QuerySnapshot> fetchGroupsByMemberArrayAsStream(
String field, dynamic condition) {
return _api.streamCollectionByArrayAny('groups', field, condition);
}
//function to get Contact Detail using List of Group User
Future<ContactModel> getContactDetail(List<dynamic> members) async {
//remove current user id from the list
members.removeWhere((element) => element.userId == user.userId);
//getContactbyId return a ContactModel object from Firestore
ContactModel contactModel =
await getContactById(user.userId, members.first.userId);
if (contactModel != null && contactModel.userId.isNotEmpty) {
return contactModel;
} else {
return new ContactModel(
userId: members.first.userId, nickname: "", photoUrl: "");
}
}
Future<GroupModel> generateGroupMessage(GroupModel group) async {
//check if Group Chat or Private chat
if (group.type == 1) {
ContactModel contactModel = await getContactDetail(group.membersList);
group.groupName = contactModel.nickname.isNotEmpty
? contactModel.nickname
: contactModel.userId;
group.groupPhoto = contactModel.photoUrl;
}
print("Add");
//add the group data into List<GroupModel> groups
groups.add(group);
return group;
}
void refreshMessageList() {
groups = [];
print("refresh");
//get Group Data as Stream from FireStore base on the user data in the Member Array of Group then map it to Stream while also change data base on Group type in generateGroupMessage
groupStream = fetchGroupsByMemberArrayAsStream('membersList', [
{"isActive": true, "role": 1, "userId": user.userId},
{"isActive": true, "role": 2, "userId": user.userId}
]).asyncMap((docs) => Future.wait([
for (GroupModel group in docs.docs
.map((doc) => GroupModel.fromMap(doc.data()))
.toList())
generateGroupMessage(group)
]));
}
Message.dart
#override
void initState() {
super.initState();
...
databaseService.refreshMessageList();
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.symmetric(horizontal: 16),
margin: EdgeInsets.only(top: 24),
child: Column(
children: [
...
Flexible(
child: StreamBuilder(
stream: databaseService.groupStream,
builder: (context, AsyncSnapshot<List<GroupModel>> snapshot) {
if (!snapshot.hasData) {
print("No data");
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.grey),
),
);
} else {
print("Has data");
groups = List.from(snapshot.data);
groups.removeWhere(
(element) => element.recentMessageContent.isEmpty);
groups.sort((group1, group2) {
if (DateTime.parse(group1.recentMessageTime)
.isAfter(DateTime.parse(group2.recentMessageTime))) {
return -1;
} else {
return 1;
}
});
return ListView.builder(
padding: EdgeInsets.all(10.0),
itemBuilder: (context, index) =>
buildItem(context, groups[index]),
itemCount: groups.length,
),
),
),
}
],)));
}
Widget buildItem(BuildContext context, GroupModel group) {
if (group.recentMessageContent == '') {
return Container();
} else {
return Column(
children: [
Container(
child: InkWell(
child: Row(
children: <Widget>[
Material(
child: group.groupPhoto.isNotEmpty
? CachedNetworkImage(
placeholder: (context, url) => Container(
child: CircularProgressIndicator(
strokeWidth: 1.0,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.grey),
),
width: 60.0,
height: 60.0,
padding: EdgeInsets.all(10.0),
),
imageUrl: group.groupPhoto,
width: 60.0,
height: 60.0,
fit: BoxFit.cover,
)
: Icon(
group.type == 1
? Icons.account_circle
: Icons.group,
size: 60.0,
color: Colors.grey,
),
borderRadius: BorderRadius.all(Radius.circular(30.0)),
clipBehavior: Clip.hardEdge,
),
SizedBox(
width: 150,
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
group.groupName,
style: TextStyle(
color: colorBlack,
fontSize: 12,
fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis,
),
Text(
group.recentMessageContent,
style: TextStyle(
color: Colors.grey,
fontSize: 10,
height: 1.6),
overflow: TextOverflow.ellipsis,
),
],
),
margin: EdgeInsets.only(left: 12.0),
),
),
Spacer(),
Text(
formatDateTime(group.recentMessageTime),
style: TextStyle(color: Colors.grey, fontSize: 10),
),
],
),
onTap: () {
switch (group.type) {
case 1:
Navigator.of(context, rootNavigator: true)
.push(MaterialPageRoute(
settings:
RouteSettings(name: "/message/chatPage"),
builder: (context) => ChatPage(group: group)))
.then((value) => setState);
break;
case 2:
Navigator.of(context, rootNavigator: true)
.push(MaterialPageRoute(
settings:
RouteSettings(name: "/message/chatGroup"),
builder: (context) =>
ChatGroupPage(group: group)))
.then((value) => {setState(() {})});
break;
}
}),
),
Divider(
color: Colors.grey,
),
],
);
}
}
The ChatPage and ChatGroupPage navigate to Private Chat and Group Chat respectively, and in there the User can add the chat partner or group member into Contact.
When adding is done I call the databaseService.refreshMessageList to refresh the Stream of List Group, so when I navigate back to the Message Screen, it will refresh and display accordingly. However, the List<GroupModel> groups becomes blank and will not add data until I navigate back to the Message Screen.
I debugged the app and found that the List became blank because it executes groups = [] but did not run the .asyncMap until I hot reload or navigate Message Screen and put the setState in .then to refresh the data.
I need the List groups to check whether the 2 users already have a private chat to create a new one when adding to Contact. I have already tried putting setState after databaseService.refreshMessageList, but it still did not work.
Can anyone please help me and provide a solution? I know this is not a good question to ask, but I have been stuck with this for almost a week now and desperately need an answer. Thank you in advance.
EDIT
Here is my data structure:
Users
/users (collection)
/userId
/user (document)
- userId
- nickname
- photoUrl
- token
- /contacts (subcollection)
/contactId
/contact (document)
- userId
- nickname
- photoUrl
Groups:
/groups (collection)
/groupId
/group (document)
- groupId
- groupName
- type
- membersList (List<Map<String, dynamic>>)
- member: userId, isActive, role
- recentMessageContent
- recentMessageTime
- recentMessageType
Messages:
/messages (collection)
/groupId
/groupMessage (document)
/messages (subcollection)
/messageId
/message (document)
- messageContent
- messageTime
- messageType

You can use array membership, for example, the array-contains method can query for elements within an array without performing any manipulation. There is an interesting article that provides some examples you might interest you.
Another alternative could be to iterate both arrays until matching the values you need. However, iteration can lead to performance issues if you do not implement it correctly.

Related

Getting specific data values from Realtime Database in Flutter

I'm working on a project with Firebase (Realtime database). In this project I will have a main screen with will have several buttons according to the user. The Buttons info are going to be stored inside the realtime database. This is basically a Home Automation project.
This is how my db looks:
The quantity, means how many buttons does that user have. button1 and button2 have the button characteristics. So what I'm attempting to do is.
When the user logs in. I have a Streambuilder that will check if the quantity has data. If I has if will run inside a For loop which will create the buttons in the user screen.
I having problem getting the specific values from the database, for example, getting the quantity and storing into a variable in the main screen.
This is how I'm attempting to get the quantity (I will use this code for getting other values too, later on) but it isn't working:
Future<int> receive_quantity() async{
final FirebaseUser user = await _auth.currentUser();
var snapshot = databaseReference.child(user.uid+"/buttons"+"/quantity").once();
var result;
await snapshot.then((value) => result = value);
print(result);
return result;
}
Error that I get:
_TypeError (type 'DataSnapshot' is not a subtype of type 'FutureOr<int>')
My StreamBuilder:
body: StreamBuilder(
stream: _auth.getButtonQuantity(),
initialData: 0,
builder: (context, snapshot) {
if (snapshot.hasError || snapshot.hasError){
return Container(color: Colors.red);
}
if (!snapshot.hasData || !snapshot.hasData){
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasData || snapshot.hasData){
return GridView.count(
padding: EdgeInsets.all(15),
crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0,
crossAxisCount: 3,
children: [
for (int i = 0; i < buttonquant; i++){
Button(),
},
GestureDetector(
onTap: () async{
_auth.receive_quantity();
},
child: Container(
color: Colors.black,
width: 150,
height: 150,
child: Icon(Icons.add, color: Colors.white,),
),
),
],
);
}
}
),
My Button:
class Button extends StatelessWidget {
const Button({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
},
child: Container(
width: 150,
height: 150,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(15)
),
child: Stack(
children: [
Positioned(
top: 10,
left: 10,
child: Icon(
Icons.lightbulb,
size: 35,
color: Colors.white,
),
),
Positioned(
top: 95,
left: 15,
child: Text("Televisao", style: TextStyle(color: Colors.white),),
),
],
),
),
);
}
}```
What you need to do is you need to get the value of the snapshot not using it directly:
Future<int> receive_quantity() async{
final FirebaseUser user = await _auth.currentUser();
var snapshot = await databaseReference.child(user.uid+"/buttons"+"/quantity").once();
var result = snapshot.value; //get the value here
print(result);
return result;
}
This is how you get the value in general:
databaseReference.once().then((DataSnapshot snapshot) {
print('Data : ${snapshot.value}');
});

How can i get data from a subcollection in firebase in flutter?

i m building an app using flutter and firebase as backend .
i m storring my data in a collection named 'Users' . every user has data and has a subcollection named 'Transactions' which are the transactions that he made .
i want to get these informations
Here is my DataBase
enter image description here
enter image description here
class DatabaseService {
final String uid;
DatabaseService({this.uid});
final CollectionReference usersCollection =
FirebaseFirestore.instance.collection('Users');
List<Transaction1> TransactionsListFromSnapshot(QuerySnapshot snapshot) {
return snapshot.docs.map((doc) {
return Transaction1(
uid: doc.data()['uid'],
name: doc.data()['name'],
description: doc.data()['description'],
time: doc.data()['time'],
argent: doc.data()['argent'],
somme: doc.data()['somme'],
deleted: doc.data()['deleted']);
}).toList();
}
Stream<List<Transaction1>> get transactions{
return
usersCollection.doc(uid).collection('Transactions').snapshots().map(TransactionsListFromSnapshot);
}
}
and this is the flutter code
body: StreamBuilder<List<Transaction1>>(
stream: DatabaseService(uid: this.widget.uid).transactions,
builder: (context, snapshot) {
return snapshot.hasData ? Stack(
children: [
ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
print('hhhhh');
return Transaction_Card(data: snapshot.data[index]);
},
),
Positioned(
right: 20,
bottom: 20,
child: FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.white,
),
onPressed: () {
_showMyDialog();
},
))
],
) : Stack(
children: [
Text('Error'),
Positioned(
right: 20,
bottom: 20,
child: FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.white,
),
onPressed: () {
_showMyDialog();
},
))
],
);
}),
Can anyone help me please
I just did this yesterday. In my parent document I add an array in which I store the document id of the sub collection documents.
Then, in the repository I have the following function (projects are the main documents, areas are the sub collection documents
#override
Stream<List<Area>> areas(String projectId) {
final CollectionReference areaCollection =
FirebaseFirestore.instance.collection('projects').doc(projectId).collection('areas');
return areaCollection.snapshots().map((snapshot) {
return snapshot.docs
.map((doc) => Area.fromEntity(AreaEntity.fromSnapshot(doc)))
.toList();
});
}
Then, I have used the example of the bloc documentation with entities and models to transform the data.
For each (sub) collection I have a separate listener, so one for projects, and one for areas of my currently focussed project. If new areas arrive, I check whether I already know them (by id) or if I have to add a new area to the project.
for (var a in event.areas) {
if (!areaPointer.containsKey(a.areaId)) { // add new area to list
areaPointer[a.areaId] = project.areas.length;
project.areas.add(a);
}
else // update existing area
project.areas[areaPointer[a.areaId]] = a;
}
As you see, I also have an areaPointer which stores the relationship of area.id and index of project.areas[i]

Having issues trying to get array data out of my collection

I am trying to retrieve all the uid's who liked a comment, to display the list of people on the page. I am having issue querying this data using StreamBuilder. I've tried it so many different ways that I've seen on here but I'm not having any luck. I got different errors or no data at all before this. Can anyone help me out with this issue?
This is the database path
/comments/411f47a0-8404-4800-a32e-35c260d7b670/comments/3ygnNwXM3WQlmoNDV6Ac
So comments/postId/comments/ then the comment data is here with the array of "likers" containing each uid
Right now, I'm getting this error. And it's pointing the the return StreamBuilder line.
type 'QuerySnapshot' is not a subtype of type 'DocumentSnapshot'
This is the widget that I have the StreamBuilder in.
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: Firestore.instance
.collection("comments")
.document(widget.postId)
.collection("comments")
.where("likers", arrayContains: widget.uid)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var user = User.fromDocument(snapshot.data);
return Padding(
padding: const EdgeInsets.only(top: 5.0, left: 5.0, right: 5.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(15.0),
child: Card(
child: ListTile(
tileColor: R.colors.grey200,
leading: user.photoUrl.isNotEmpty
? CachedNetworkImage(
placeholder: (context, url) => CircleAvatar(
backgroundColor: R.colors.grey,
radius: 25.0,
),
imageUrl: user.photoUrl,
width: 50.0,
height: 50.0,
fit: BoxFit.cover,
)
: Icon(
Icons.account_circle,
size: 50.0,
color: R.colors.grey,
),
title: Text(
(user.username),
style: TextStyle(
color: R.colors.black, fontWeight: FontWeight.bold),
),
subtitle: Text(
(user.profileName),
style: TextStyle(color: R.colors.black),
),
trailing: FlatButton(
color: R.colors.blueAccent,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(25.0))),
clipBehavior: Clip.hardEdge,
onPressed: () {
if (!following) {
controlFollowUser();
} else {
controlUnfollowUser();
}
},
child: Text(following
//currentUser.following.contains(user.id)
? "Unfollow"
: "Follow")),
),
),
),
);
} else {
return ListTile(
title: Text("Nothing here"),
);
}
});
}
This line is getting the user data like the username, and profile picture and it's able to get that because it would have the correct uid's from the comment data that was queried. Here is where it originates from.
var user = User.fromDocument(snapshot.data);
factory User.fromDocument(DocumentSnapshot doc) {
if (doc != null) {
return User(
following: doc['following']?.cast<String>() ?? [],
followers: doc['followers']?.cast<String>() ?? [],
id: doc.documentID,
email: doc['email'] ?? "",
username: doc['username'],
photoUrl: doc['photoUrl'],
url: doc['photoUrl'],
profileName: doc['profileName'],
bio: doc['bio'],
createdAt: doc['createdAt'],
talkingTo: doc['talkingTo'],
receiverName: doc['receiverName'],
);
} else {
return new User();
}
}
Any help would be appreciated.
The error message is telling you that snapshot.data returns a QuerySnapshot object, but you're trying to pass it to a function that takes a DocumentSnapshot as a parameter. They're not compatible. A QuerySnapshot represent the results of a query that could return zero or more documents. A DocumentSnapshot represents a single document.
If you want to process the results of this query, you will have to iterate the documents in the QuerySnapshot and deal with them individually. There is an example in the documentation:
querySnapshot.docs.forEach((doc) {
print(doc["first_name"]);
});
If your query could return multiple user documents You're going to have to iterate the results and decide what to do with each document. If you want to pass each DocumentSnapshot to User.fromDocument(), that would compile (but I don't know if it would do what you want, since we can't see the data from your query).

StreamBuilder not updating after an item is removed Flutter

I am new to Flutter and this is my first time asking a question on Stackoverflow. I apologize for any misunderstanding. I will try my best to make it clear.
I am using sqflite for storing user's favorites and populating a list from the DB on a page, named Favorites screen. This Favorites page is one of the items on my bottom navbar.
My issue is that when I tap on an item from the favorites list which takes me to a screen where I can unfavorite that item. I double-checked that it is really removed from the DB by logging the rows count. But when I go back to the Favorites page, that item is still on the list. If I go to one of the pages from the bottom navbar and go back to the Favorites screen, the item isn't there. I understand that the page is being rebuilt again this time but my intention was the Stream will constantly listen for a change.
I have also implemented a slide to dismiss feature on the fav screen, which works as intended. But I am using the same logic on both.
StreamBuilder code in Favorite screen
StreamBuilder<List<WeekMezmurList>>(
stream: favBloc.favStream,
builder: (context, AsyncSnapshot<List<WeekMezmurList>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Text(
"Loading Favorites...",
style: TextStyle(fontSize: 20),
),
);
} else if (snapshot.data == null) {
return Center(
child: Text(
"No Favorites yet!",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
);
} else {
return ListView.builder(
physics: BouncingScrollPhysics(),
padding: const EdgeInsets.fromLTRB(5.0, 10.0, 5.0, 10.0),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return new GestureDetector(
onTap: () =>
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
AudioPlayerScreen(
mezmurName: snapshot.data[index].mezmurName,
),
),
),
child: Slidable(
key: new Key(snapshot.data[index].mezmurName),
actionPane: SlidableDrawerActionPane(),
actionExtentRatio: 0.25,
// closes other active slidable if there is any
controller: slidableController,
secondaryActions: <Widget>[
IconSlideAction(
caption: 'Share',
color: Colors.indigo,
icon: Icons.share,
onTap: () =>
_share(snapshot
.data[index]),
),
IconSlideAction(
caption: 'Delete',
color: Colors.red,
icon: Icons.delete,
onTap: () =>
_swipeDelete(
context, snapshot.data[index].mezmurName),
),
],
child: Card(
color: Colors.white,
child: Padding(
padding: EdgeInsets.symmetric(
vertical: 15.0,
horizontal: 10.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(
children: <Widget>[
_misbakChapter(
snapshot.data[index].misbakChapters),
SizedBox(width: 15),
_displayFavoritesMisbakLines(
snapshot.data[index], index),
],
)
],
),
),
),
),
);
},
);
}
},
);
slide to delete code in Favorites screen
// deletes the specific favorite from the sqflite db
Future<void> _swipeDelete(BuildContext context, String mezmurName) async {
try {
favBloc.delete(mezmurName);
} catch (e) {
CupertinoAlertDialog(
content: Text("Something went wrong. Please try again."),
actions: <Widget>[
CupertinoDialogAction(
child: Text(
"Ok",
),
onPressed: () => Navigator.of(context).pop(),
),
],
);
}
}
I have the same logic in the second screen, the screen I get when I tap on one of the items from the Fav list.
favBloc.delete(widget.mezmurName);
BLoC code, I got the concepts from this Medium article
class FavoritesBloc{
FavoritesBloc(){
getFavorites();
}
final databaseHelper = DatabaseHelper.instance;
// broadcast makes it to start listening to events
final _controller = StreamController<List<WeekMezmurList>>.broadcast();
get favStream => _controller.stream;
void dispose() {
_controller.close();
}
getFavorites () async{
_controller.sink.add(await databaseHelper.getFavorites());
}
insert(WeekMezmurList fav){
databaseHelper.insertToDb(fav);
getFavorites();
}
delete(String mezmurName){
databaseHelper.delete(mezmurName: mezmurName);
getFavorites();
}
}
Delete method in the DB class
// deleting a value from the db
delete({String mezmurName}) async {
var dbClient = await getDb;
try {
await dbClient
.delete(TABLE, where: '$MEZMUR_NAME = ?', whereArgs: [mezmurName]);
} catch (e) {
}
}
I have tried to research this issue but all I have found were for remote databases.
Just to make it more clear, I took a screen record.
Thank you in advance!
The reason why StreamBuilder on the first screen doesn't update with the changes made is because it uses a different instance of FavoritesBloc(). If you'd like for the bloc to be globally accessible with a single instance, you can declare it as
final favBloc = FavoritesBloc();
Otherwise, you can follow what has been suggested in the comments and pass FavoritesBloc as an argument between screens.

Multiple streams to firebase documents without disposing

I am trying to add chatting to my app.
When my user starts a chat with a new user, I create a chatroom with a unique id in my Firebase database. I want my user to be notified with any updates of the chatroom document( eg: new messages) so when I create a chatroom, I also create a new stream to that chatroom document. Is there a problem with constantly listening to many documents with different streams without disposing the streams (because I want to get the latest results of any chatroom that user is a member of.)
This is my chatroom_screen code:
import 'package:flutter/material.dart';
import '../models/databse_management.dart';
class ChatroomScreen extends StatefulWidget {
static const String routeName = "/chatroom_screen";
#override
_ChatroomScreenState createState() => _ChatroomScreenState();
}
class _ChatroomScreenState extends State<ChatroomScreen> {
TextEditingController _messageTextEditingController = TextEditingController();
bool isChatroomExists;
Stream chatroomDocStream; //my initial stream variable which is null
#override
Widget build(BuildContext context) {
final Map<String, dynamic> passedArguments =
ModalRoute.of(context).settings.arguments;
//sedner details can be retrieved from currentUserDetails
final String senderId = passedArguments["senderId"];
final List<String> receiversIds = passedArguments["receiverIds"];
final String senderUsername = passedArguments["senderUsername"];
final List<String> receiverUsernames = passedArguments["receiverUsernames"];
final String chatroomId = passedArguments["chatroomId"];
if(chatroomDocStream == null){
chatroomDocStream = Firestore.instance.collection("chatrooms").document(chatroomId).snapshots(); //if no stream was created before (first time we build the widget), we connect a stream to this chatroom documentspecified with chatroomId
}
isChatroomExists = isChatroomExists == null
? passedArguments["isChatroomExists"]
: isChatroomExists;
final Image senderProfilePictureUrl =
passedArguments["senderProfilePicture"];
final List<Image> receiverProfilePictures =
passedArguments["receiverProfilePictures"];
final mediaQuery = MediaQuery.of(context).size;
final ThemeData theme = Theme.of(context);
//we get the values from the passed argument map
if (isChatroomExists) {
//load the previous chats
}
return Scaffold(
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
CircleAvatar(
backgroundImage: receiverProfilePictures[0]
.image, //right now only for 1 receiver but change it for multi members later
),
Container(
child: Text(receiverUsernames[0]),
margin: const EdgeInsets.only(left: 10),
),
],
),
),
body: //to do=> create a stream that is listening to the chatroom document for any changes
Stack(
children: <Widget>[
StreamBuilder(
//updates the chats whenever data of the chatroom document changes
stream: chatroomDocStream,
builder: (context, snapShot) {
return Column(
children: <Widget>[
Center(child: const Text("My chats"))
//message widgets go here
],
);
},
),
Positioned(
//positioned is used for positioning the widgets inside a stack. bottom: 10 means 10 pixel from bottom
bottom: 0,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
width: mediaQuery.width, //takes the total width of the screen
decoration: BoxDecoration(
color: theme.primaryColor.withOpacity(0.3),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
//always takes the remaining space (textField widget use this as its size https://stackoverflow.com/questions/45986093/textfield-inside-of-row-causes-layout-exception-unable-to-calculate-size)
child: Container(
padding: const EdgeInsets.symmetric(
horizontal:
10), //horizontal padding for the textfield widget
decoration: BoxDecoration(
color: theme.accentColor,
borderRadius: BorderRadius.circular(25),
border: Border.all(width: 2, color: theme.primaryColor),
),
child: TextField(
minLines: 1,
maxLines: 5,
controller: _messageTextEditingController,
decoration: const InputDecoration(
hintText: "Type something here...",
border: InputBorder
.none //removes all the border for textfield widget
),
),
),
),
Container(
child: Row(
//another row for buttons to be treated all toghether as a container in the parent row
children: <Widget>[
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
//add media like image or pictures
},
),
IconButton(
icon: const Icon(Icons.camera_alt),
onPressed: () {
//take picture or video
},
),
IconButton(
icon: const Icon(Icons.send),
onPressed: () async {
if (_messageTextEditingController.text
.trim()
.isEmpty) {
return;
}
try {
await DatabaseManagement().sendMessage(
membersIds: [
senderId,
...receiversIds
], //extracts all the members of the list as seperate String items
//to do=>later on we have to get a list of the members as well
isChatroomExists: isChatroomExists,
chatroomId: chatroomId,
messageContent:
_messageTextEditingController.text,
senderId: senderId,
timestamp: Timestamp
.now(), //it's from firebase and records the time stamp of the sending message
);
if (isChatroomExists != true) {
isChatroomExists =
true; //after we sent a messsage, we 100% created the chatroom so it becomes true
}
} catch (e) {
print(
e.toString(),
);
return;
}
},
)
],
),
)
],
),
),
),
],
),
);
}
}
The idea is to have something like WhatsApp which you receive a notification of any updates in any chatroom you are a member of.
You can create a lot of snapshot listeners, but Google recommends a limit of 100 snapshot listeners per client:
Source: https://cloud.google.com/firestore/docs/best-practices?hl=en#realtime_updates

Resources