Update Chat Screen
🗣️ Update Chat Screen
Now let's finish the Chat application and save the chat message to database.
Database Service
We update our DatabaseService to getChatMessages and sendChatMessage => database_service.dart
Future<List<Message>> getChatMessages(String senderId, String receiverId) async {List<Message> messages = [];QuerySnapshot messagesSenderQuerySnapshot = await chatsRef.where('senderId', isEqualTo: senderId).where('toUserId', isEqualTo: receiverId).orderBy('timestamp', descending: true).get();for (var doc in messagesSenderQuerySnapshot.docs) {// ignore: avoid_printprint(doc.id);messages.add(Message.fromDoc(doc));}QuerySnapshot messagestoQuerySnapshot = await chatsRef.where('senderId', isEqualTo: receiverId).where('toUserId', isEqualTo: senderId).orderBy('timestamp', descending: true).get();for (var doc in messagestoQuerySnapshot.docs) {// ignore: avoid_printprint(doc.id);messages.add(Message.fromDoc(doc));}Comparator<Message> timestampComparator =(a, b) => b.timestamp!.compareTo(a.timestamp!);messages.sort(timestampComparator);return messages;}void sendChatMessage(Message message) {chatsRef.add({'senderId': message.senderId,'toUserId': message.toUserId,'text': message.text,'imageUrl': message.imageUrl,'isLiked': message.isLiked,'unread': message.unread,'timestamp': Timestamp.fromDate(DateTime.now()),});}
Update Message Model
We need to update our message model and introduce
final Timestamp? timestamp;
also it will have a function that maps database values with object.
import 'package:cloud_firestore/cloud_firestore.dart';class Message {final String? id, senderId, toUserId, text, imageUrl;final bool? isLiked;final bool? unread;final Timestamp? timestamp;Message({this.id,this.senderId,this.toUserId,this.text,this.imageUrl,this.isLiked,this.unread,this.timestamp,});factory Message.fromDoc(DocumentSnapshot doc) {return Message(id: doc.id,senderId: doc['senderId'],toUserId: doc['toUserId'],text: doc['text'],imageUrl: doc['imageUrl'],isLiked: doc['isLiked'],unread: doc['unread'],timestamp: doc['timestamp']);}}
We will add dateFormat to our constants.dart 📄 file
To get the dateformat import package - intl - pubspec.yaml
intl: ^0.17.0
final DateFormat timeFormat = DateFormat('E, h:mm a');
Refactor Chat Screen
That chat_screen.dart
will need to take 2 parameters, from which user message is coming from and to sender.
final String currentUserId;final AppUser toUser;const ChatScreen({required this.currentUserId, required this.toUser, Key? key}): super(key: key);@override_ChatScreenState createState() => _ChatScreenState();}
We need to bring messages from our database. so remove reference to dummy data
List<Message> _messages = [];
And add database service , also that needs to be initialized
, create a private function to process through the messages and initialize the message list
List<Message> _messages = [];late DatabaseService _databaseService;@overridevoid initState() {super.initState();_databaseService = Provider.of<DatabaseService>(context, listen: false);_setupMessages();}_setupMessages() async {List<Message> messages = await _databaseService.getChatMessages(widget.currentUserId, widget.toUser.id!);setState(() {_messages = messages;});}
Update message time in the text with
Text(timeFormat.format(message.timestamp!.toDate()),
In the void _handleSubmitted(String
, update code to pass new values and call the database service
void _handleSubmitted(String text) {_textMessageController.clear();setState(() {_isComposing = false;});Message message = Message(senderId: widget.currentUserId,toUserId: widget.toUserId,timestamp: Timestamp.fromDate(DateTime.now()),text: text,isLiked: true,unread: true,);setState(() {_messages.insert(0, message);});_databaseService.sendChatMessage(message);}
And finally update the isMe
code to get value from the widget
final bool isMe = message.senderId == widget.currentUserId;
Chat screen is called through the attendees screen.
Update all_attendees_widget
So update the all_attendees_widget.dart
Note : In the build get the current user id
Widget build(BuildContext context) {String? currentUserId =Provider.of<UserData>(context, listen: false).currentUserId;
And pass it to the chatscreen , update GestureDetector
return GestureDetector(onTap: () => Navigator.push(context,MaterialPageRoute(builder: (_) => ChatScreen(currentUserId: currentUserId!,toUser: user,),),),
You may receive an indexing error, go to console and click on the link to rebuild index
Error:
FirebaseException ([cloud_firestore/failed-precondition] The query requires an index. You can create it here: ..
Sending Picture Messages
We would also like to send pictures in the chat.
In our storage_service.dart we will add/check function uploadMessageImage
Future<String> uploadMessageImage(File imageFile) async {String imageId = const Uuid().v4();File? image = await _compressImage(imageId, imageFile);String downloadUrl = await _uploadImage('images/messages/message_$imageId.jpg',image!,);return downloadUrl;}
In chat_screen.dart we update _buildMessage
And check if message.imageurl == null and remove Text(message.text!,
… and refactor into its own functions
children: <Widget>[message.imageUrl == null? _buildText(isMe, message): _buildImage(context, message),const SizedBox(height: 8.0),
Add the new methods before build
_buildText(bool isMe, Message message) {return Text(message.text!,style: TextStyle(color: isMe? AppConstants.hexToColor(AppConstants.APP_PRIMARY_COLOR_BLACK): AppConstants.hexToColor(AppConstants.APP_BACKGROUND_COLOR_GRAY),fontSize: 15.0,fontWeight: FontWeight.w600,),);}_buildImage(BuildContext context, Message message) {final size = MediaQuery.of(context).size;return Container(height: size.height * 0.2,width: size.width * 0.6,decoration: BoxDecoration(borderRadius: BorderRadius.circular(20.0),image: DecorationImage(fit: BoxFit.cover,image: CachedNetworkImageProvider(message.imageUrl!),)),);}
Add to onPressed of our camera icon _buildMessageComposer()
RawMaterialButton(onPressed: () async {XFile? imageFile = await ImagePicker().pickImage(source: ImageSource.gallery,);if (imageFile != null) {String imageUrl =await Provider.of<StorageService>(context, listen: false).uploadMessageImage(File(imageFile.path));_handleSubmitted(null, imageUrl);}},child: Icon(Icons.camera_alt,
Update _handleSubmitted to take additional parameter
void _handleSubmitted(String? text, String? imageUrl) {if ((text != null && text.trim().isNotEmpty) || imageUrl != null) {if (imageUrl == null) {//text messagesetState(() {_isComposing = false;});}Message message = Message(senderId: widget.currentUserId,toUserId: widget.toUser.id,imageUrl: imageUrl,timestamp: Timestamp.fromDate(DateTime.now()),text: text,isLiked: true,unread: true,);setState(() {_messages.insert(0, message);});_databaseService.sendChatMessage(message);}}
And update its usages.
onPressed: _isComposing? () => _handleSubmitted(_textMessageController.text, null): null,
Finally add Provider for StorageService in main.dart
Provider<StorageService>(create: (_) => StorageService(),),