I’m encountering a problem in my Flutter (Dart) code for a chat group I’m building. The issue arises in the chat screen where, upon testing, all messages appear on the right side, even when sent by different users. How can I ensure proper alignment of messages in my Flutter chat app, where messages sent by different users appear on the correct side of the screen, and include relevant user information such as sender name and timestamp? I’ve included the relevant code snippets below. Any assistance would be greatly appreciated. Thank you!
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:file_picker/file_picker.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:intl/intl.dart';
String formatTime(DateTime time) {
return DateFormat('HH:mm').format(time);
}
class Message {
final String id;
final String content;
final String senderId;
final String senderName;
final DateTime timestamp;
final String appogee;
final String cne;
Message({
required this.id,
required this.content,
required this.senderId,
required this.senderName,
required this.timestamp,
required this.appogee,
required this.cne,
});
}
class MessageBubble extends StatelessWidget {
final Message message;
final bool isSentByCurrentUser;
final VoidCallback onDeletePressed;
final List<QueryDocumentSnapshot> chatGroupId;
// ignore: annotate_overrides, overridden_fields
final Key? key; // Add Key parameter
const MessageBubble({
this.key, // Pass Key parameter to superclass constructor
required this.message,
required this.isSentByCurrentUser,
required this.onDeletePressed,
required this.chatGroupId,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onLongPress: () => _showMessageOptions(context, message, isSentByCurrentUser, chatGroupId),
child: Align(
alignment: isSentByCurrentUser ? Alignment.centerLeft : Alignment.centerRight, // Swap alignment condition
child: _messageBubble(message, context),
),
);
}
Widget _messageBubble(Message message, BuildContext context) {
return Row(
mainAxisAlignment: isSentByCurrentUser ? MainAxisAlignment.start : MainAxisAlignment.end, // Adjust MainAxisAlignment based on isSentByCurrentUser
children: [
if (!isSentByCurrentUser) ...[
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Text(
message.senderName,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
),
],
Flexible(
child: Container(
padding: EdgeInsets.all(message.content.contains('https://') ? MediaQuery.of(context).size.width * .03 : MediaQuery.of(context).size.width * .04),
margin: EdgeInsets.symmetric(horizontal: MediaQuery.of(context).size.width * .04, vertical: MediaQuery.of(context).size.height * .01),
decoration: BoxDecoration(
color: isSentByCurrentUser ? Colors.blueGrey : const Color.fromARGB(255, 33, 43, 48),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(isSentByCurrentUser ? 0 : 30),
topRight: Radius.circular(isSentByCurrentUser ? 30 : 0),
bottomLeft: const Radius.circular(30),
bottomRight: const Radius.circular(30),
),
),
child: !message.content.contains('https://')
? Text(
message.content,
style: TextStyle(fontSize: 15, color: isSentByCurrentUser ? Colors.black : Colors.white),
)
: ClipRRect(
borderRadius: BorderRadius.circular(15),
child: Image.network(
message.content,
errorBuilder: (context, error, stackTrace) {
return Icon(Icons.image, size: 70, color: isSentByCurrentUser ? Colors.black87 : Colors.white);
},
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null,
),
);
},
),
),
),
),
],
);
}
// Add _showMessageOptions method here
void _showMessageOptions(BuildContext context, Message message, bool isSentByCurrentUser, List<QueryDocumentSnapshot> chatGroupId) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Message Options'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Content: ${message.content}'),
const SizedBox(height: 8),
Text('Sender: ${message.senderName}'),
const SizedBox(height: 8),
Text('Timestamp: ${message.timestamp}'),
],
),
actions: [
if (isSentByCurrentUser)
TextButton(
onPressed: () {
Navigator.of(context).pop();
_showMessageUpdateDialog(context, message, isSentByCurrentUser, chatGroupId);
},
child: const Text('Update'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
_deleteMessage(message.id, chatGroupId); // Call the delete callback
},
child: const Text('Delete'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Close'),
),
],
);
},
);
}
// Add _showMessageUpdateDialog method here
void _showMessageUpdateDialog(BuildContext context, Message message, bool isSentByCurrentUser, List<QueryDocumentSnapshot> chatGroupId) {
String updatedMsg = message.content;
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Row(
children: [
Icon(
Icons.message,
color: Colors.blue,
size: 28,
),
Text(' Update Message')
],
),
content: TextFormField(
initialValue: updatedMsg,
maxLines: null,
onChanged: (value) => updatedMsg = value,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
),
),
),
actions: [
MaterialButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text(
'Cancel',
style: TextStyle(color: Colors.blue, fontSize: 16),
),
),
MaterialButton(
onPressed: () {
Navigator.pop(context);
_updateMessage(updatedMsg, message.id, chatGroupId);
},
child: const Text(
'Update',
style: TextStyle(color: Colors.blue, fontSize: 16),
),
)
],
),
);
}
void _updateMessage(String updatedMsg, String messageId, List<QueryDocumentSnapshot> chatGroupId) {
FirebaseFirestore.instance
.collection('EST-groupes')
.doc(chatGroupId.isNotEmpty ? chatGroupId[0].id : null)
.collection('messages')
.doc(messageId)
.update({
'content': updatedMsg,
}).then((_) {
// Update successful
if (kDebugMode) {
print('Message updated successfully');
}
}).catchError((error) {
// Handle errors
if (kDebugMode) {
print('Failed to update message: $error');
}
});
}
// In _deleteMessage and _updateMessage methods
void _deleteMessage(String messageId, List<QueryDocumentSnapshot> chatGroupId) {
FirebaseFirestore.instance
.collection('EST-groupes')
.doc(chatGroupId.isNotEmpty ? chatGroupId[0].id : null)
.collection('messages')
.doc(messageId)
.delete()
.then((_) {
// Deletion successful
if (kDebugMode) {
print('Message deleted successfully');
}
})
.catchError((error) {
// Handle errors
if (kDebugMode) {
print('Failed to delete message: $error');
}
});
}
}
class CommunicationGroupPage extends StatefulWidget {
final String cne;
final String appogee;
final String filiere;
final int anneeScolaire;
final List<QueryDocumentSnapshot> chatGroupId;
final String senderName; // Add senderName parameter
const CommunicationGroupPage({
required this.cne,
required this.appogee,
required this.filiere,
required this.anneeScolaire,
required this.chatGroupId,
required this.senderName, // Include senderName parameter
super.key, // Add key parameter
}); // Call super constructor with key parameter
@override
// ignore: library_private_types_in_public_api
_CommunicationGroupPageState createState() => _CommunicationGroupPageState();
}
class _CommunicationGroupPageState extends State<CommunicationGroupPage> {
final TextEditingController _messageController = TextEditingController();
bool _isSendingMessage = false;
late FlutterSoundRecorder _audioRecorder;
@override
void initState() {
super.initState();
_audioRecorder = FlutterSoundRecorder();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Communication Group'),
backgroundColor: const Color.fromARGB(255, 255, 152, 0),
),
body: Stack(
children: [
Image.asset(
'lib/images/pattdark.png',
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
),
Column(
children: [
Expanded(
child: _buildChatInterface(context),
),
_buildChatInput(context),
],
),
],
),
);
}
Widget _buildChatInterface(BuildContext context) {
return StreamBuilder(
stream: _getChatMessagesStream(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {
return const Center(child: Text('Say Hi! ????'));
}
final List<Message> messages = snapshot.data!.docs.map((doc) {
final data = doc.data() as Map<String, dynamic>;
return Message(
id: doc.id,
content: data['content'] ?? '', // Changed 'content' to 'text'
senderId: data['sender'] ?? '',
senderName: data['senderName'] ?? '', // Using senderName from data
timestamp: (data['timestamp'] as Timestamp).toDate(),
appogee: data['appogee'] ?? '', // Added 'appogee'
cne: data['cne'] ?? '', // Added 'cne'
);
}).toList();
messages.sort((a, b) => a.timestamp.compareTo(b.timestamp)); // Sort messages by timestamp
return ListView.builder(
reverse: false, // Show new messages at the bottom
itemCount: messages.length,
itemBuilder: (context, index) {
final message = messages[index];
final isSentByCurrentUser = message.senderId == widget.cne; // Assuming widget.cne holds the current user's ID
return MessageBubble(
message: message,
isSentByCurrentUser: isSentByCurrentUser,
onDeletePressed: () => _deleteMessage(message.id),
chatGroupId: widget.chatGroupId,
);
},
);
},
);
}
Stream<QuerySnapshot> _getChatMessagesStream() {
if (widget.chatGroupId.isEmpty) {
return FirebaseFirestore.instance.collection('EST-groupes').doc().collection('messages').orderBy('timestamp').snapshots();
} else {
return FirebaseFirestore.instance.collection('EST-groupes').doc(widget.chatGroupId[0].id).collection('messages').orderBy('timestamp').snapshots();
}
}
Widget _buildChatInput(BuildContext context) {
return Container(
padding: const EdgeInsets.all(8.0),
color: Colors.grey[200],
child: Row(
children: [
IconButton(
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) => EmojiPicker(
onEmojiSelected: (category, emoji) {
_messageController.text += emoji.emoji;
},
),
);
},
icon: const Icon(Icons.emoji_emotions),
),
Expanded(
child: GestureDetector(
onTapDown: (_) {
_startRecordingVoiceMessage();
},
onTapUp: (_) {
_stopRecordingVoiceMessage();
},
onTapCancel: () {
_stopRecordingVoiceMessage();
},
child: TextField(
controller: _messageController,
decoration: InputDecoration(
hintText: 'Type your message...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide.none,
),
filled: true,
fillColor: Colors.white,
),
onTap: () {
setState(() {
_isSendingMessage = true;
});
},
onChanged: (text) {
setState(() {
_isSendingMessage = text.isNotEmpty;
});
},
onSubmitted: (_) {
if (_isSendingMessage) {
_sendMessage(context);
} else {
_startRecordingVoiceMessage();
}
},
),
),
),
IconButton(
onPressed: () async {
FilePickerResult? result = await FilePicker.platform.pickFiles();
if (result != null) {
PlatformFile file = result.files.first;
// ignore: use_build_context_synchronously
_sendFileMessage(context, file);
}
},
icon: const Icon(Icons.attach_file),
),
IconButton(
onPressed: () {
_sendMessage(context);
_messageController.clear(); // Clear the message
},
icon: _isSendingMessage ? const Icon(Icons.send) : const Icon(Icons.mic),
),
],
),
);
}
void _startRecordingVoiceMessage() async {
try {
await _audioRecorder.openRecorder();
await _audioRecorder.startRecorder(toFile: 'path_to_save_recording');
// ignore: avoid_print
print('Recording started.');
} catch (e) {
// ignore: avoid_print
print('Error starting recording: $e');
}
}
void _stopRecordingVoiceMessage() async {
try {
await _audioRecorder.stopRecorder();
await _audioRecorder.closeRecorder();
// ignore: avoid_print
print('Recording stopped.');
// Now you can handle the recorded audio file (e.g., send it in a chat message)
} catch (e) {
// ignore: avoid_print
print('Error stopping recording: $e');
}
}
void _sendFileMessage(BuildContext context, PlatformFile file) {
String senderName = widget.senderName;
if (kDebugMode) {
print('File selected: ${file.name}');
}
FirebaseFirestore.instance
.collection('EST-groupes')
.doc(widget.chatGroupId.isNotEmpty ? widget.chatGroupId[0].id : null)
.collection('messages')
.add({
'content': 'File selected: ${file.name}',
'sender': senderName,
'timestamp': DateTime.now(),
});
}
// ignore: unused_element
void _sendVoiceMessage() {
// ignore: avoid_print
print('Voice message sent');
}
Future<void> _sendMessage(BuildContext context) async {
String messageText = _messageController.text.trim();
if (messageText.isNotEmpty) {
String senderName = widget.senderName;
try {
await FirebaseFirestore.instance
.collection('EST-groupes')
.doc(widget.chatGroupId.isNotEmpty ? widget.chatGroupId[0].id : null)
.collection('messages')
.add({
'content': messageText,
'sender': senderName,
'senderName': senderName, // Add senderName field
'timestamp': DateTime.now(),
});
_messageController.clear(); // Clear the message
} catch (e) {
// Handle error
if (kDebugMode) {
print('Failed to send message: $e');
}
}
}
}
Future<void> _deleteMessage(String messageId) async {
try {
await FirebaseFirestore.instance
.collection('EST-groupes')
.doc(widget.chatGroupId.isNotEmpty ? widget.chatGroupId[0].id : null)
.collection('messages')
.doc(messageId)
.delete();
} catch (e) {
// Handle error
if (kDebugMode) {
print('Failed to delete message: $e');
}
}
}
}
[in the image you see that two uses how there messages are]](https://i.sstatic.net/TMnZC1LJ.jpg)
Fatimzahra Hammaoui is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.