Flutter tutorials and courses by Andrea Bizzotto

How to build a Chat Messaging UI in Flutter

Have you ever wanted to build a chat app and wondered how to create this messaging UI?

Chat Messaging UI in Flutter

While this UI looks simple, there are a few things to consider:

  • All text messages are shown inside a "chat bubble" with rounded corners and a fill color
  • Chat bubbles are left-aligned or right-aligned depending on who sent the message
  • The text in each bubble should wrap if it doesn't fit in one line

So let's see how to build this!

Along the way, we'll learn how to decorate, align and add custom styling to our widgets.

Creating a ChatBubble widget

As a first step, we can create a custom widget class to represent our chat bubble:

class ChatBubble extends StatelessWidget { const ChatBubble({ Key? key, required this.text, required this.isCurrentUser, }) : super(key: key); final String text; final bool isCurrentUser; // TODO: Implement build method }

This widget takes text and isCurrentUser as arguments, and needs to determine the alignment and style of the chat bubble.

For reference, we may use it like this in the parent widget:

ListView( children: const [ ChatBubble( text: 'How was the concert?', isCurrentUser: false, ), ChatBubble( text: 'Awesome! Next time you gotta come as well!', isCurrentUser: true, ), ChatBubble( text: 'Ok, when is the next date?', isCurrentUser: false, ), ChatBubble( text: 'They\'re playing on the 20th of November', isCurrentUser: true, ), ChatBubble( text: 'Let\'s do it!', isCurrentUser: false, ), ], )

In real-world apps, all the chat data would come from a backend and should be displayed with ListView.builder to ensure that only visible chat bubbles are rendered.

Next, let's work out what goes into the build method.

1. Adding a DecoratedBox

Step one is to put a Text widget inside a DecoratedBox like so:

@override Widget build(BuildContext context) { return DecoratedBox( // chat bubble decoration decoration: BoxDecoration( color: isCurrentUser ? Colors.blue : Colors.grey[300], borderRadius: BorderRadius.circular(16), ), child: Padding( padding: const EdgeInsets.all(12), child: Text( text, style: Theme.of(context).textTheme.bodyText1!.copyWith( color: isCurrentUser ? Colors.white : Colors.black87), ), ), ); }

Note: We could have used a Container as an alternative to DecoratedBox. But Container does a lot of things under the hood and DecoratedBox is more lightweight, so we can use it for better performance.

If we run this code, the chat bubbles will appear like this:

Chat bubbles: first attempt

Not a bad start, but we don't want the chat bubbles to stretch horizontally. And we could do with some padding.

2. Adding an Align widget

Let's fix this layout by adding Align and Padding widgets:

@override Widget build(BuildContext context) { return Padding( // add some padding padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), child: Align( // align the child within the container alignment: isCurrentUser ? Alignment.centerRight : Alignment.centerLeft, child: DecoratedBox( // chat bubble decoration decoration: BoxDecoration( color: isCurrentUser ? Colors.blue : Colors.grey[300], borderRadius: BorderRadius.circular(16), ), child: Padding( padding: const EdgeInsets.all(12), child: Text( text, style: Theme.of(context).textTheme.bodyText1!.copyWith( color: isCurrentUser ? Colors.white : Colors.black87), ), ), ), ), ); }

The most important line is this:

alignment: isCurrentUser ? Alignment.centerRight : Alignment.centerLeft,

This will ensure that each chat bubble is aligned correctly and doesn't fill all the available width:

Chat Messaging UI: wide layout

This is much better and the text wraps over multiple lines if needed:

Chat Messaging UI: narrow layout

Let's do one final tweak.

3. Final tweak: asymmetric padding

The chat bubble should include a bit of padding on the left or right side to prevent it from using the full available width when the text wraps.

Here's how to do it:

@override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.fromLTRB( isCurrentUser ? 64.0 : 16.0, 4, isCurrentUser ? 16.0 : 64.0, 4, ), child: Align(...) ); }

And here's how it looks when the text wraps:

Chat Messaging UI: narrow layout with asymmetric padding

That's it! We built a responsive chat bubble UI that we can reuse in our apps.

Here's the full code for the ChatBubble class:

class ChatBubble extends StatelessWidget { const ChatBubble({ Key? key, required this.text, required this.isCurrentUser, }) : super(key: key); final String text; final bool isCurrentUser; @override Widget build(BuildContext context) { return Padding( // asymmetric padding padding: EdgeInsets.fromLTRB( isCurrentUser ? 64.0 : 16.0, 4, isCurrentUser ? 16.0 : 64.0, 4, ), child: Align( // align the child within the container alignment: isCurrentUser ? Alignment.centerRight : Alignment.centerLeft, child: DecoratedBox( // chat bubble decoration decoration: BoxDecoration( color: isCurrentUser ? Colors.blue : Colors.grey[300], borderRadius: BorderRadius.circular(16), ), child: Padding( padding: const EdgeInsets.all(12), child: Text( text, style: Theme.of(context).textTheme.bodyText1!.copyWith( color: isCurrentUser ? Colors.white : Colors.black87), ), ), ), ), ); } }

Lessons learned

The completed implementation is simple and elegant.

But when I started researching this, I went down the wrong route and attempted to use a Row to align things. And I ended up using LayoutBuilder and ConstrainedBox to fight some layout issues. If you're curious about this approach, here's a Twitter thread:

So sometimes it's best to take a step back and look for a simpler solution.

Albert Einstein said it best:

"Everything should be made as simple as possible, but not simpler."

Turns out, this is great advice for Flutter app development. 😀

Happy coding!

Want more?

Fast-track your Flutter learning with over 40 hours of in-depth content.