image

29 Jan 2026

9K

35K

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.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is