Building a Chat Bubble Widget with Timestamp and Avatar in Flutter
In modern chat applications, a well-designed message bubble is crucial for a smooth and intuitive user experience. It effectively displays the message content, sender information, and when it was sent. This article will guide you through creating a versatile chat bubble widget in Flutter, complete with an avatar, timestamp, and adaptive styling for both sender and receiver messages.
Understanding the Core Components
Before diving into the code, let's break down the essential elements we'll be implementing:
- Message Text: The primary content of the chat bubble.
- Timestamp: Indicates when the message was sent, typically displayed in a smaller, subtler font.
- Avatar: A visual representation of the sender, usually a circular image or initial.
- Bubble Shape and Styling: The container for the message text, featuring rounded corners and distinct background colors for sender and receiver messages.
- Alignment: Messages should align to the right for the current user (sender) and to the left for others (receiver), with avatars positioned accordingly.
Step 1: Define the Message Data Model
First, we need a data structure to hold the information for each message. Create a file named message.dart inside a lib/models directory.
// lib/models/message.dart
import 'package:flutter/material.dart'; // Required for Message class due to 'Key?' in future ChatBubble, though not directly used here.
class Message {
final String text;
final DateTime timestamp;
final String sender; // e.g., "Alice", "Bob", "Me"
final bool isMe; // True if the message is sent by the current user
Message({
required this.text,
required this.timestamp,
required this.sender,
this.isMe = false,
});
}
Step 2: Create the Chat Bubble Widget
Next, we'll build the reusable ChatBubble widget. This widget will take a Message object and render it with appropriate styling, alignment, avatar, and timestamp. Create a file named chat_bubble.dart inside lib/widgets.
// lib/widgets/chat_bubble.dart
import 'package:flutter/material.dart';
import '../models/message.dart'; // Ensure correct path to your Message model
class ChatBubble extends StatelessWidget {
final Message message;
const ChatBubble({Key? key, required this.message}) : super(key: key);
// Helper method to format timestamp
String _formatTimestamp(DateTime timestamp) {
return '${timestamp.hour.toString().padLeft(2, '0')}:'
'${timestamp.minute.toString().padLeft(2, '0')} '
'${timestamp.day}/${timestamp.month}';
}
@override
Widget build(BuildContext context) {
// Determine alignment and colors based on sender
final alignment =
message.isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start;
final mainAxisAlignment =
message.isMe ? MainAxisAlignment.end : MainAxisAlignment.start;
final bubbleColor =
message.isMe ? Colors.blue[200] : Colors.grey[300];
final textColor = message.isMe ? Colors.black87 : Colors.black87;
final borderRadius = BorderRadius.circular(12.0);
return Container(
// Padding around the entire message row for visual separation
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
// Align the entire message row (avatar + bubble) left or right
alignment: message.isMe ? Alignment.centerRight : Alignment.centerLeft,
child: Row(
mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Avatar for the receiver (if message is NOT from 'me')
if (!message.isMe) ...[
CircleAvatar(
backgroundColor: Colors.blueGrey,
child: Text(
message.sender.substring(0, 1).toUpperCase(),
style: const TextStyle(color: Colors.white),
),
),
const SizedBox(width: 8.0), // Space between avatar and bubble
],
// The actual message bubble content
Flexible(
child: Column(
crossAxisAlignment: alignment, // Align text and timestamp inside bubble
children: [
Container(
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: bubbleColor,
borderRadius: borderRadius,
),
// Limit the bubble width to prevent it from spanning the whole screen
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.75),
child: Text(
message.text,
style: TextStyle(color: textColor),
),
),
const SizedBox(height: 4.0), // Space between bubble and timestamp
Text(
_formatTimestamp(message.timestamp),
style: TextStyle(
fontSize: 10.0,
color: Colors.grey[600],
fontStyle: FontStyle.italic),
),
],
),
),
// Avatar for the sender (if message IS from 'me')
if (message.isMe) ...[
const SizedBox(width: 8.0), // Space between bubble and avatar
CircleAvatar(
backgroundColor: Colors.green[300],
child: const Icon(Icons.person, color: Colors.white), // Generic icon for 'Me'
),
],
],
),
);
}
}
Step 3: Integrate into a Chat Screen
To see our ChatBubble in action, let's create a simple chat screen that displays a list of messages and allows sending new ones. Create chat_screen.dart in lib/screens.
// lib/screens/chat_screen.dart
import 'package:flutter/material.dart';
import '../models/message.dart'; // Ensure correct path
import '../widgets/chat_bubble.dart'; // Ensure correct path
class ChatScreen extends StatefulWidget {
const ChatScreen({Key? key}) : super(key: key);
@override
State createState() => _ChatScreenState();
}
class _ChatScreenState extends State {
// Sample messages to display
final List _messages = [
Message(
text: "Hello! How are you doing today?",
timestamp: DateTime.now().subtract(const Duration(minutes: 5)),
sender: "Alice",
isMe: false,
),
Message(
text: "I'm doing great, thanks for asking! What about you?",
timestamp: DateTime.now().subtract(const Duration(minutes: 3)),
sender: "Me",
isMe: true,
),
Message(
text: "I'm good too! Just working on a Flutter chat app.",
timestamp: DateTime.now().subtract(const Duration(minutes: 1)),
sender: "Alice",
isMe: false,
),
Message(
text: "Sounds interesting! Let me know if you need help.",
timestamp: DateTime.now(),
sender: "Me",
isMe: true,
),
];
final TextEditingController _textController = TextEditingController();
void _sendMessage() {
if (_textController.text.isNotEmpty) {
setState(() {
_messages.add(
Message(
text: _textController.text,
timestamp: DateTime.now(),
sender: "Me",
isMe: true,
),
);
});
_textController.clear(); // Clear the input field
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Flutter Chat"),
),
body: Column(
children: [
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(8.0),
reverse: false, // Set to true for chat apps that scroll from bottom
itemCount: _messages.length,
itemBuilder: (context, index) {
return ChatBubble(message: _messages[index]);
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _textController,
decoration: InputDecoration(
hintText: "Enter message...",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20.0),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16.0),
),
onSubmitted: (_) => _sendMessage(), // Send message on enter key
),
),
const SizedBox(width: 8.0),
FloatingActionButton(
onPressed: _sendMessage,
mini: true,
child: const Icon(Icons.send),
),
],
),
),
],
),
);
}
@override
void dispose() {
_textController.dispose();
super.dispose();
}
}
Step 4: Update main.dart
Finally, update your main.dart file to run your new ChatScreen.
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:your_app_name/screens/chat_screen.dart'; // Adjust import based on your project structure
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Chat Bubble Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const ChatScreen(),
);
}
}
Conclusion
You have successfully built a sophisticated chat bubble widget in Flutter that includes message text, dynamic alignment, an avatar, and a timestamp. This modular approach allows for easy customization and integration into any Flutter chat application, providing a solid foundation for a beautiful and functional messaging interface. Further enhancements could include different message types (images, videos), read receipts, and more complex animations.