How to have both sides view same chat in flutter - flutter

I was following this tutorial https://www.youtube.com/watch?v=wHIcJDQbBFs&t= and got it working so I can send messages and it is stored as follows
chats/targetUid/messages/message
when I send a message it works and I can see them but if I sign into the account im sending it to they cannot see it. I believe its due to the following reasons.
In the tutorial you get data from the targets messages so when I open the chat its getting it from his Uid as the target but when he opens it it uses my Uid as the target.
This is my send message code.
//create Comment
Message newMessage = Message(
profImage: myProfilePic,
username: myName,
uid: myUid,
message: message,
createdAt: DateTime.now(),
);
final refMessages = _firestore.collection('chats/$targetUid/messages');
await refMessages.add(
newMessage.toJson(),
);
The code to open the message.
return ListView.builder(
itemCount: (snapshot.data! as dynamic).docs.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ChatScreen(
uid: (snapshot.data! as dynamic).docs[index]['uid'],
),
),
);
},
child: ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(
(snapshot.data! as dynamic).docs[index]['photoUrl'],
),
),
title: Text(
(snapshot.data! as dynamic).docs[index]['username'],
),
),
);
},
);

I fixed it by creating a groupchat Id and storing it in firestore that way rather than the targets Uid. this is the method im using to create the groupchat Id.
String chatroomId(String user1, String user2) {
if (user1[0].toLowerCase().codeUnits[0] >
user2.toLowerCase()[0].codeUnits[0]) {
return "$user1$user2";
} else {
return "$user2$user1";
}
}

Related

Firestore get length data disappear in flutter stream

I make a chat using firebase firestore.
So. I tried express not read count in the Chat list.
But, Initially the number appears, but it changes to null data.
I don't know Why data chage null data?
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('chatRooms')
.where('emails', arrayContainsAny: [user?.email]).snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
var chatLists = snapshot.data?.docs;
if (snapshot.hasError) {
return Text('Something error...');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text('is Loading...');
}
return ListView.builder(
itemCount: chatLists?.length,
itemBuilder: (context, index) {
if (chatLists?[index]['currentMsg'] != null &&
chatLists?[index]['currentMsg'] != "") {
var list = List.from(chatLists?[index]['members']);
var member = '';
if (loginUser['userName'] != null) {
for (int i = 0; i < list.length; i++) {
if (list[i] != loginUser['userName']) {
member += list[i];
}
}
}
return ListTile(
title: Row(
children: [
Text(member),
const SizedBox(
width: 20.0,
),
ChatLength(docId: chatLists![index].id, uid: user!.uid),
],
),
subtitle: SizedBox(
height: 40.0,
child: Text(
chatLists[index]['currentMsg'],
overflow: TextOverflow.ellipsis,
)),
trailing: Text(
tmFormmater(chatLists?[index]['currentTm']),
style: const TextStyle(
color: Color(0xff999999),
),
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatScreen(
docId: chatLists![index].id,
title: member,
),
)),
);
} else {
return Container();
}
},
);
return Container();
},
),
class ChatLength extends StatelessWidget {
const ChatLength({super.key, required this.docId, required this.uid});
final String docId;
final String uid;
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('message_read')
.doc(docId)
.collection('message')
.where('userId', isNotEqualTo: uid)
.where('read', isEqualTo: false)
.snapshots(),
builder: (context, snapshot) {
print(snapshot.data?.size);
if (snapshot.data?.size != null) {
return Container(
child: Text('${snapshot.data?.size}'),
);
} else {
return Container();
}
},
);
}
}
===== Debug console ====
flutter: 1
flutter: null // Data changes to null immediately
message_read collection structure
message_read -> documentId -> message -> documentId -> field(userId(chat writer), read)
I'm trying get a snapshot data from firestore.
And I put the imported data into the text.
But Data changes to null immediately.
Since you use snapshots() the code is not just reading the data once, but then continues listening for changes to the data. So the two values you see mean that the snapshots() stream was triggered twice, the first time with a single message - and the second time without any messages.
My educated guess is that your code changes the read value of the document after it gets it, since it has now displayed that message to the user. But doing so means the document no longer meets the criteria of your query, so the snapshots stream gets a new event without that message in it.
Consider using a different mechanism for the query, for example I usually use a timestamp to determine what messages to show. Step by step:
Ensure each message document has a timestamp field.
For each user store (either in the database or in local storage of the app) when they last started the app.
Then request from the database the messages since they last started the app.
Make sure to start the query before you update the timestamp value, otherwise you'll never get any results.

StreamUnreadIndicator does not update and displays nothing getStream Api

I have had tough luck with the StreamUnreadIndicator() within the getStream API. I am trying to essentially have an indicator on the list tile for whenever a new message comes in. But nothing returns. I tried putting some debug prints to at least get the number of unread messages for the channel, but it is always 0.
Here's my message list view:
Widget _messagesList(List<dynamic>? messages, StreamChatClient client,
int messageCount, bool friendsTab) {
return ListView.separated(
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
itemCount: messageCount,
itemBuilder: (context, index) {
//print("messaging:"+messages![index].channel);
return GestureDetector(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) =>
MessageApi(
sourceType: SourceType.justMet,
receiverUser: friendsTab ? friends[index] : chatRequesters[index],
userName: userName,
channelId: messages![index].channel,
streamToken: streamToken,
client: StreamChatCore.of(context).client,
)
));
},
child: ListTile(
title: friendsTab ? Text(friends[index].firstName) : Text(chatRequesters[index].firstName),
subtitle: _buildLastMessage(messages![index].channel, client),
trailing: Column(
children: [
StreamUnreadIndicator(
cid: "messaging:"+messages[index].channel,
),
_buildLastMessageAt(messages[index].channel, client),
],
),
leading: CircleAvatar(
radius: 30,
backgroundImage: CachedNetworkImageProvider(
friendsTab ? friends[index].photoUrl : chatRequesters[index].photoUrl
),
),
),
);
},
separatorBuilder: (context, index) {
return const Divider();
},
);
}
Version 4.3 of Stream Chat Flutter introduced unreadMessagesSeparatorBuilder:
Try
StreamMessageListView(
unreadMessagesSeparatorBuilder: (context, unreadCount) =>
Text('$unreadCount unread message'),
)
See the changeling for additional details: https://pub.dev/packages/stream_chat_flutter/changelog
They seem to have updated their backend and now it works without me changing anything. I noticed they changed their docs recently too after this question.

How do I setup a scoreboard in flutter? (using firebase)

Hi I'm having trouble setting up my scoreboard in flutter, everything is working as intended except one part. I want to create a scoreboard which displays a username, image and some data. All the data is stored in a collection in firebase and I have set it up as following.
date "2022-05-12"
time 20220512
träning "data"
userID "x"
I would like to get the username and the image from another collection where I've saved the user info. The user documents looks like this and they are named after the userID.
name "x"
imagePath "x"
My original idea was to get the data and the userID and then use the userID to get the name and image. This is my current code. Im getting the correct userID but I cant figure out how to get the name and the image from the user documents. Does anyone know how I could solve this? I'll add an example screenshot at the end. Thanks for reading.
StreamBuilder<QuerySnapshot<Object?>> buildStreamBuilderTop() {
return StreamBuilder<QuerySnapshot>(
stream: trViewTop,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Nått gick snett');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text('Datan hämtas');
}
final data = snapshot.requireData;
return AnimationLimiter(
child: ListView.builder(
itemCount: data.size,
itemBuilder: (context, index) {
QueryDocumentSnapshot user = snapshot.data!.docs[index];
String userID = user['userID'];
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(seconds: 1),
child: SlideAnimation(
verticalOffset: 44,
child: FadeInAnimation(
child: Card(
child: ListTile(
leading: CircleAvatar(radius: 30, backgroundImage: NetworkImage(*this is where i want the image*)),
title: Text(*this is where i want the name*),
subtitle: Text('Träningar ${data.docs[index]['totalTr']}'),
trailing: Text(' + ${data.docs[index]['totalTr'] * 20 } Kr',
style: TextStyle(color: Colors.green),),
),
),
),
),
);
}
),
);
},
);
}

Favourite Button in Flutter got unFavourited on Relaunch app

I have a ListView Item with Favourite icon and I want to add functionality so that I can add list into favourite list. data is successfully added to favourite list.
Here is HomePage
body: ListView.builder(
itemCount: 100,
cacheExtent: 20.0,
padding: const EdgeInsets.symmetric(vertical: 16),
itemBuilder: (context, index) => ItemTile(index),
),
and My ListTile class I used
var favoritesList = Provider.of<Favorites>(context);
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.primaries[index % Colors.primaries.length],
),
title: Text(
'Item $index',
key: Key('text_$index'),
),
trailing: IconButton(
key: Key('icon_$index'),
icon: favoritesList.items.contains(index)
? Icon(Icons.favorite, color: Colors.redAccent)
: Icon(Icons.favorite_border),
onPressed: () {
!favoritesList.items.contains(index)
? favoritesList.add(index)
: favoritesList.remove(index);
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(favoritesList.items.contains(index)
? 'Added to favorites.'
: 'Removed from favorites.'),
duration: Duration(seconds: 1),
),
);
},
),
),
I have a model class favourites.dart
class Favorites extends ChangeNotifier {
final List<int> _favoriteItems = [];
List<int> get items => _favoriteItems;
void add(int itemNo) {
_favoriteItems.add(itemNo);
notifyListeners();
}
void remove(int itemNo) {
_favoriteItems.remove(itemNo);
notifyListeners();
}
}
and in my favouritePage. I am getting everything perfect and also can remove favourited item but when I reopen my app I did not get any favourited item.
here is my page FavouritePage.
body: Consumer<Favorites>(
builder: (context, value, child) => ListView.builder(
itemCount: value.items.length,
padding: const EdgeInsets.symmetric(vertical: 16),
itemBuilder: (context, index) => FavoriteItemTile(value.items[index]),
),
),
FavouriteItemTile
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.primaries[itemNo % Colors.primaries.length],
),
title: Text(
'Item $itemNo',
key: Key('favorites_text_$itemNo'),
),
trailing: IconButton(
key: Key('remove_icon_$itemNo'),
icon: Icon(Icons.close),
onPressed: () {
Provider.of<Favorites>(context, listen: false).remove(itemNo);
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Removed from favorites.'),
duration: Duration(seconds: 1),
),
);
},
),
),
please provide the solution and can I use shared preferences with provider.
Yes. You should be using SharedPreferences. Add the preference library and these pieces of code
Object.dart
class Object1{
bool isLiked;
String name;
const Object1(this.name,this.isLiked);//Whatever fields you need
factory User.fromJson(Map<String, dynamic> parsedJson) {
return new Object1(
name: parsedJson['name'] ?? "",
isLiked: parsedJson['isLiked'] ?? "");
}
Map<String, dynamic> toJson() {
return {
"name": this.name,
"isLiked": this.isLiked
};
}
}
Main.dart
void main(){
setData();
runApp(MyApp);
}
void setData() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
List dataList = [Object1("Name",false).toJson()];//Your items in this format
if prefs.getStringList("lists") == null:
Map decode_options = jsonDecode(dataList);
prefs.setStringList(jsonEncode(Object1.fromJson(decode_options)));
}
Now instead of a custom class for favourites, we will get all the data where we can filter. To retrieve the data afterwards, use this code
SharedPreferences prefs = await SharedPreferences.getInstance();
Map objectMap = jsonDecode(await shared_User.getStringList('list'));
List itemList = [];
for (item in objectMap):
itemList.append(User.fromJson(item));
Now you can use this Item list with the properties and the isLiked feature which is a boolean to check whether it is showed or not.
This may seem complicated but is perfectly simple though your work would be much easier if you used a database like firebase and stored these as documents
One option can be that you can store according to index value in shared preference and query that index value in order to see whether it is added as favourite or not. However it won't be efficient as the number of favourites increases, though still an option.
If you want to store on device us File(pathToFile).write(dataAsString)
You might want to save the data as json using jsonEncode(listOfNumbers) and decode using jsonDecode()
Explanation:
To save data, convert it to json and save to File on device
// NOTE: dataAsMap should be instance of class Map which stores the data you want to save
Directory localDirectory = await getApplicationDocumentsDirectory();
File(localDirectory.path + “/“ + “fileName.json”).writeAsStringSync(jsonEncode(dataAsMap));
To get data:
Map jsonData = jsonDecode(File(localDirectory.path + “/“ + “fileName.json”).readAsStringSync())

I am getting error while using stream builder in flutter

i am making a mobile app using flutter. And i am using stream builder for this screen. I am not getting the point where i am wrong in the code. Can you please help me in this. I am sharing code and screenshot for this particular row which is causing problem
var timeSelected = 'Click here';
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
'Time Slot:',
style: TextStyle(color: Colors.white),
),
Spacer(),
GestureDetector(
onTap: () {
_asyncInputDialog(context);
//_displayDialog();
},
child: StreamBuilder(stream: cartManager.getTimeSlotSelected,
initialData: timeSelected,
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData){
timeShow(snapshot,);
}
else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center(
child: Container(
child: Text('Select time slot'),
),
);
},)
),
],
),
This alert dialog will show when i click on the text of row:
_asyncInputDialog(
BuildContext context,
) {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Center(child: Text('Available Time Slot')),
content: TEAlertDialogContent(),
actions: <Widget>[
new FlatButton(
child: new Text('CANCEL'),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
});
}
When i got the value from showdialog i will store the value in streamcontroller that is present in CartManager.
static StreamController<Timeslot> timeSlotController = BehaviorSubject();
timeSlotSelected(Timeslot time){
timeSlotController.sink.add(time);
}
get getTimeSlotSelected{
return timeSlotController.stream;
}
And we call the above method in stream property of streamcontroller and get the snapshot. This is the method which was called when our snapshot has data:
Widget timeShow(AsyncSnapshot<Timeslot> snapshot ) {
timeSelected = '${snapshot.data.firstTimeSlot}-${snapshot.data.secondTimeSlot}';
timeslotid = snapshot.data.id.toString();
return Text(timeSelected);
}
But i am getting error: type 'BehaviorSubject' is not a subtype of type 'Stream'
Please let me know where i am wrong. I had also shared a screen shot of screen showing this error too.
As your error states, you are trying to pass a type Timeslot to a Stream builder expecting a stream of type String. You must check which one is correct (String or Timeslot) and use the same type on both sides.
Apparently, your problem is in the timeSelected variable. Where is it defined? If this is a String, the Stream builder will infer that your stream is of type String, which is not true. You must set this variable as a Timeslot, since this is your stream type.
Also, you have an error in your code. You have to return a widget to be rendered if snapshot has data. Check the code below:
StreamBuilder(stream: cartManager.getTimeSlotSelected,
initialData: timeSelected,
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData){
return timeShow(snapshot,);
}
else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center(
child: Container(
child: Text('Select time slot'),
),
);
},)