chat_bubbles 1.9.0
chat_bubbles: ^1.9.0 copied to clipboard
Flutter chat bubble widgets, similar to Whatsapp and more shapes. Easy to use and implement chat bubbles.
chat_bubbles plugin #
Flutter chat bubble widgets, similar to the Whatsapp and more shapes. Audio and Image chat bubble widgets are also included. Easy to use and implement chat bubbles.
Getting Started #
Add this to your package's pubspec.yaml file:
dependencies:
chat_bubbles: ^1.9.0
Usage #
Then you just have to import the package with
import 'package:chat_bubbles/chat_bubbles.dart'
Now you can use this plugin to implement various types of Chat Bubbles, Audio Chat Bubbles and Date chips.
Examples #
iMessage's bubble example #
BubbleSpecialThree(
text: 'Added iMessage shape bubbles',
color: Color(0xFF1B97F3),
tail: false,
textStyle: TextStyle(
color: Colors.white,
fontSize: 16
),
),
BubbleSpecialThree(
text: 'Please try and give some feedback on it!',
color: Color(0xFF1B97F3),
tail: true,
textStyle: TextStyle(
color: Colors.white,
fontSize: 16
),
),
BubbleSpecialThree(
text: 'Sure',
color: Color(0xFFE8E8EE),
tail: false,
isSender: false,
),
BubbleSpecialThree(
text: "I tried. It's awesome!!!",
color: Color(0xFFE8E8EE),
tail: false,
isSender: false,
),
BubbleSpecialThree(
text: "Thanks",
color: Color(0xFFE8E8EE),
tail: true,
isSender: false,
)
Single bubble example #
BubbleSpecialOne(
text: 'Hi, How are you? ',
isSender: false,
color: Colors.purple.shade100,
textStyle: TextStyle(
fontSize: 20,
color: Colors.purple,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold,
),
),
Audio chat bubble example #
Duration duration = new Duration();
Duration position = new Duration();
bool isPlaying = false;
bool isLoading = false;
bool isPause = false;
BubbleNormalAudio(
color: Color(0xFFE8E8EE),
duration: duration.inSeconds.toDouble(),
position: position.inSeconds.toDouble(),
isPlaying: isPlaying,
isLoading: isLoading,
isPause: isPause,
onSeekChanged: _changeSeek,
onPlayPauseButtonClick: _playAudio,
sent: true,
),
Image chat bubble example #
BubbleNormalImage(
id: 'id001',
image: _image(),
color: Colors.purpleAccent,
tail: true,
delivered: true,
),
Date Chip example #
DateChip(
date: new DateTime(2021, 5, 7),
color: Color(0x558AD3D5),
),
Message bar example #
MessageBar(
onSend: (_) => print(_),
actions: [
InkWell(
child: Icon(
Icons.add,
color: Colors.black,
size: 24,
),
onTap: () {},
),
Padding(
padding: EdgeInsets.only(left: 8, right: 8),
child: InkWell(
child: Icon(
Icons.camera_alt,
color: Colors.green,
size: 24,
),
onTap: () {},
),
),
],
),
New in v1.8.0 #
Reply/Quote Bubble example #
BubbleReply(
repliedMessage: 'This is the original message',
repliedMessageSender: 'John Doe',
text: 'This is my reply!',
isSender: false,
color: Color(0xFF1B97F3),
replyBorderColor: Colors.white,
textStyle: TextStyle(color: Colors.white, fontSize: 16),
sent: true,
),
Typing Indicator example #
TypingIndicator(
showIndicator: true,
bubbleColor: Color(0xFFE8E8EE),
dotColor: Colors.black54,
),
// Alternative wave animation style
TypingIndicatorWave(
showIndicator: true,
bubbleColor: Color(0xFFE8E8EE),
dotColor: Colors.black54,
),
Link Preview Bubble example #
BubbleLinkPreview(
url: 'https://flutter.dev',
title: 'Flutter - Build apps for any screen',
description: 'Flutter transforms the app development process.',
imageUrl: 'https://storage.googleapis.com/cms-storage-bucket/70760bf1e88b184bb1bc.png',
text: 'Check out this awesome framework!',
isSender: false,
color: Color(0xFF1B97F3),
textStyle: TextStyle(color: Colors.white, fontSize: 16),
),
Bubble Reactions example #
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
BubbleNormal(
text: 'Great work! 🎉',
isSender: false,
color: Color(0xFF1B97F3),
),
BubbleReaction(
reactions: [
Reaction(emoji: '👍', count: 3, isUserReacted: true),
Reaction(emoji: '❤️', count: 2),
Reaction(emoji: '🎉', count: 1),
],
onReactionTap: (reaction) {
print('Tapped on ${reaction.emoji}');
},
onAddReactionTap: () {
print('Add reaction tapped');
},
),
],
),
Main example (Chat View) #

Checkout the plugin example to figure out more.
Duration duration = new Duration();
Duration position = new Duration();
bool isPlaying = false;
bool isLoading = false;
bool isPause = false;
@override
Widget build(BuildContext context) {
final now = new DateTime.now();
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Stack(
children: [
SingleChildScrollView(
child: Column(
children: <Widget>[
BubbleNormalImage(
id: 'id001',
image: _image(),
color: Colors.purpleAccent,
tail: true,
delivered: true,
),
BubbleNormalAudio(
color: Color(0xFFE8E8EE),
duration: duration.inSeconds.toDouble(),
position: position.inSeconds.toDouble(),
isPlaying: isPlaying,
isLoading: isLoading,
isPause: isPause,
onSeekChanged: _changeSeek,
onPlayPauseButtonClick: _playAudio,
sent: true,
),
BubbleNormal(
text: 'bubble normal with tail',
isSender: false,
color: Color(0xFF1B97F3),
tail: true,
textStyle: TextStyle(
fontSize: 20,
color: Colors.white,
),
),
BubbleNormal(
text: 'bubble normal with tail',
isSender: true,
color: Color(0xFFE8E8EE),
tail: true,
sent: true,
),
DateChip(
date: new DateTime(now.year, now.month, now.day - 2),
),
BubbleNormal(
text: 'bubble normal without tail',
isSender: false,
color: Color(0xFF1B97F3),
tail: false,
textStyle: TextStyle(
fontSize: 20,
color: Colors.white,
),
),
BubbleNormal(
text: 'bubble normal without tail',
color: Color(0xFFE8E8EE),
tail: false,
sent: true,
seen: true,
delivered: true,
),
BubbleSpecialOne(
text: 'bubble special one with tail',
isSender: false,
color: Color(0xFF1B97F3),
textStyle: TextStyle(
fontSize: 20,
color: Colors.white,
),
),
DateChip(
date: new DateTime(now.year, now.month, now.day - 1),
),
BubbleSpecialOne(
text: 'bubble special one with tail',
color: Color(0xFFE8E8EE),
seen: true,
),
BubbleSpecialOne(
text: 'bubble special one without tail',
isSender: false,
tail: false,
color: Color(0xFF1B97F3),
textStyle: TextStyle(
fontSize: 20,
color: Colors.black,
),
),
BubbleSpecialOne(
text: 'bubble special one without tail',
tail: false,
color: Color(0xFFE8E8EE),
sent: true,
),
BubbleSpecialTwo(
text: 'bubble special tow with tail',
isSender: false,
color: Color(0xFF1B97F3),
textStyle: TextStyle(
fontSize: 20,
color: Colors.black,
),
),
DateChip(
date: now,
),
BubbleSpecialTwo(
text: 'bubble special tow with tail',
isSender: true,
color: Color(0xFFE8E8EE),
sent: true,
),
BubbleSpecialTwo(
text: 'bubble special tow without tail',
isSender: false,
tail: false,
color: Color(0xFF1B97F3),
textStyle: TextStyle(
fontSize: 20,
color: Colors.black,
),
),
BubbleSpecialTwo(
text: 'bubble special tow without tail',
tail: false,
color: Color(0xFFE8E8EE),
delivered: true,
),
BubbleSpecialThree(
text: 'bubble special three without tail',
color: Color(0xFF1B97F3),
tail: false,
textStyle: TextStyle(color: Colors.white, fontSize: 16),
),
BubbleSpecialThree(
text: 'bubble special three with tail',
color: Color(0xFF1B97F3),
tail: true,
textStyle: TextStyle(color: Colors.white, fontSize: 16),
),
BubbleSpecialThree(
text: "bubble special three without tail",
color: Color(0xFFE8E8EE),
tail: false,
isSender: false,
),
BubbleSpecialThree(
text: "bubble special three with tail",
color: Color(0xFFE8E8EE),
tail: true,
isSender: false,
),
SizedBox(
height: 100,
)
],
),
),
MessageBar(
onSend: (_) => print(_),
actions: [
InkWell(
child: Icon(
Icons.add,
color: Colors.black,
size: 24,
),
onTap: () {},
),
Padding(
padding: EdgeInsets.only(left: 8, right: 8),
child: InkWell(
child: Icon(
Icons.camera_alt,
color: Colors.green,
size: 24,
),
onTap: () {},
),
),
],
),
],
),
// This trailing comma makes auto-formatting nicer for build methods.
);
}
New in v1.9.0 #
Message Status Enhancements #
All bubble widgets now accept timestamp, isEdited, isForwarded, and messageId parameters.
BubbleNormal(
text: 'This message was edited',
isSender: true,
color: Color(0xFFE8E8EE),
tail: true,
seen: true,
timestamp: '12:34 PM',
isEdited: true,
),
BubbleNormal(
text: 'Forwarded from another chat',
isSender: false,
color: Color(0xFF1B97F3),
tail: true,
textStyle: TextStyle(color: Colors.white, fontSize: 16),
isForwarded: true,
timestamp: '12:35 PM',
),
Swipe Actions #
Wrap any bubble with SwipeableBubble to add swipe-to-reply and swipe-to-delete gestures.
SwipeableBubble(
onSwipeRight: () => print('Reply triggered'),
onSwipeLeft: () => print('Delete triggered'),
swipeThreshold: 64.0,
enableHaptics: true,
child: BubbleNormal(
text: 'Swipe me!',
isSender: false,
color: Color(0xFF1B97F3),
tail: true,
textStyle: TextStyle(color: Colors.white, fontSize: 16),
),
),
| Parameter | Type | Default | Description |
|---|---|---|---|
child |
Widget |
required | The bubble to wrap |
onSwipeRight |
VoidCallback? |
null | Called on swipe-right (reply) |
onSwipeLeft |
VoidCallback? |
null | Called on swipe-left (delete) |
swipeThreshold |
double |
64.0 | Drag distance to trigger |
enableHaptics |
bool |
true | Haptic feedback on threshold |
rightActionColor |
Color |
Colors.blue | Background for right swipe |
leftActionColor |
Color |
Colors.red | Background for left swipe |
rightActionIcon |
Icon |
Icons.reply | Icon for right swipe |
leftActionIcon |
Icon |
Icons.delete | Icon for left swipe |
Message Groups / Clustering #
Use BubbleGroupBuilder to automatically group consecutive messages from the same sender. Only the last message in each group shows a tail.
final messages = ['Hello!', 'How are you?', 'Good, thanks!'];
final senders = ['alice', 'alice', 'bob'];
BubbleGroupBuilder(
itemCount: messages.length,
senderIdOf: (i) => senders[i],
itemBuilder: (context, i, info) => BubbleNormal(
text: messages[i],
isSender: senders[i] == 'bob',
tail: info.showTail,
color: senders[i] == 'bob'
? const Color(0xFFE8E8EE)
: const Color(0xFF1B97F3),
),
)
For advanced control, use MessageGroupHelper.compute() directly to get GroupInfo for each message:
final List<GroupInfo> groups = MessageGroupHelper.compute(
senderIds: senders,
timestamps: timestamps, // optional
threshold: const Duration(minutes: 2),
);
// groups[i].showTail — whether to show the tail
// groups[i].showAvatar — whether to show the avatar
// groups[i].isGroupStart — first message in group (add extra top spacing)
Voice Message Waveform #
BubbleNormalAudio now supports waveform visualization and playback speed control.
BubbleNormalAudio(
color: Color(0xFFE8E8EE),
duration: duration.inSeconds.toDouble(),
position: position.inSeconds.toDouble(),
isPlaying: isPlaying,
isLoading: isLoading,
isPause: isPause,
onSeekChanged: _changeSeek,
onPlayPauseButtonClick: _playAudio,
sent: true,
// Waveform visualization
waveformData: [0.2, 0.8, 0.5, 0.9, 0.4, 0.7, 0.6, 0.3, 0.8, 0.5],
waveformActiveColor: Color(0xFF1B97F3),
waveformInactiveColor: Colors.grey,
// Playback speed
showPlaybackSpeed: true,
playbackSpeed: _playbackSpeed,
onPlaybackSpeedChanged: (speed) => setState(() => _playbackSpeed = speed),
),
waveformData is a List<double> of normalized bar heights (0.0–1.0). Tapping or dragging on the waveform seeks to that position. The speed button cycles 1x → 1.5x → 2x → 1x.
Issues #
Please feel free to let me know any issue regarding to this plugin.
