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_print
print(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_print
print(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;
@override
void 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 isMecode 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 message
setState(() {
_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(),
),