flutter_chats_ui
Production-grade Flutter chat UI framework β plugin-based message types, command-pattern undo/redo, transport-agnostic, optimistic UI, offline outbox, threads, reactions, voice notes, polls, and more.
π» Desktop Demo (Windows)
You can try a ready-to-run Windows demo of the chat UI:
π Download Demo (.exe)
https://github.com/Brah-Timo/flutter_chats-ui/releases/tag/untagged-eefa7c5889d1fa5ca488
File:
chat_demo.exe
How to run
- Download the
.exefile from the link above - Double-click to launch
- No installation required
Notes
- Built with Flutter Windows
- Uses in-memory transport for demo purposes
- No backend required
Features
| Feature | Details |
|---|---|
| Clean Architecture | Domain β Engine β BLoC β UI β no layer leaks |
| MVVM / BLoC | ChatBloc + ChatEvent surface; StreamBuilder-friendly ChatState |
| Command Pattern | Undo/redo stacks (max 50), SendMessage, EditMessage, Delete, React, Pin, MarkRead, Reply, Batch |
| 12 Message Plugins | Text, Image, Video, Audio, Voice Note, File, Location, Poll, Contact, Code, Sticker, System Event |
| Transport Adapters | In-Memory (tests/demos), WebSocket, REST, Firestore stub |
| Offline Outbox | Queued sends with automatic flush on reconnect |
| Headless Engine | ChatEngine has zero Flutter dependency β pure Dart, fully testable |
| Threads & Replies | First-class thread support with reply counts and ThreadPanel |
| Reactions | Per-message emoji reactions with toggle (add/remove) and undo |
| Full-Text Search | SearchEngine β case-insensitive, registry-aware fallback text |
| Theming | ChatTheme with light/dark presets, copyWith, and adaptive factory |
| Accessibility | RepaintBoundary on every bubble; SafeArea-aware composer |
Getting Started
Installation
Add to your pubspec.yaml:
dependencies:
flutter_chats_ui: ^1.0.0
Then run:
flutter pub get
Minimal Example
import 'package:flutter_chats_ui/flutter_chats_ui.dart';
import 'package:flutter_chats_ui/plugins.dart';
import 'package:flutter_chats_ui/transports.dart';
final conversation = Conversation(
id: 'conv-1',
kind: ConversationKind.direct,
members: [me, peer],
messages: [],
);
// FlutterChatsUi is the ready-made StatefulWidget entry point.
FlutterChatsUi(
initialConversation: conversation,
currentUserId: me.id,
transport: MemoryTransport(seed: conversation),
)
Using ChatScreen directly
import 'package:flutter_chats_ui/flutter_chats_ui.dart';
import 'package:flutter_chats_ui/plugins.dart';
import 'package:flutter_chats_ui/transports.dart';
class MyChatPage extends StatefulWidget { /* ... */ }
class _MyChatPageState extends State<MyChatPage> {
late final ChatBloc _bloc;
@override
void initState() {
super.initState();
_bloc = ChatBloc(
initialConversation: widget.conversation,
transport: MyWebSocketTransport(url: 'wss://chat.example.com'),
currentUserId: widget.me.id,
);
}
@override
void dispose() {
_bloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChatScreen(
bloc: _bloc,
registry: MessageTypeRegistry.defaults(),
theme: ChatTheme.adaptive(context),
);
}
}
Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β UI Layer β
β ChatScreen ChatAppBar ChatMessageList MessageComposer β
β ChatBubble ThreadPanel PinnedMessagesPanel ... β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β ChatEvent / ChatState
βββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββ
β BLoC Layer β
β ChatBloc ChatEvent ChatState β
ββββββββ¬βββββββββββββββββββββ¬βββββββββββββββββββββββ¬βββββββββββ
β Commands β Engine β Transport
ββββββββΌβββββββ ββββββββββββΌββββββββββ ββββββββββΌββββββββββ
βCommandMgr β β ChatEngine β βTransportAdapter β
β undo stack β β MessageGrouper β β Memory β
β redo stack β β DateSeparator β β WebSocket β
β β β SearchEngine β β REST β
β β β UnreadMarker β β Firestore stub β
βββββββββββββββ ββββββββββββββββββββββ ββββββββββββββββββββ
β
βββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββ
β Domain Layer β
β ChatUser Message Conversation Reaction Attachment β
β Thread MessagePayload MessageTypePlugin ChatCommand β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Message Plugins
Register built-in plugins or add custom ones:
// Use all 12 built-in plugins
final registry = MessageTypeRegistry.defaults();
// Or start empty and add selectively
final registry = MessageTypeRegistry.empty()
..register(TextMessagePlugin())
..register(ImageMessagePlugin())
..register(MyCustomPlugin());
Custom Plugin
class MyStarredPayload extends MessagePayload {
final String note;
const MyStarredPayload(this.note);
@override String get typeId => 'starred';
@override Map<String, Object?> toJson() => {'note': note};
}
class MyStarredPlugin extends MessageTypePlugin {
const MyStarredPlugin();
@override String get id => 'starred';
@override String get displayName => 'Starred Note';
@override IconData get icon => Icons.star_rounded;
@override
Widget bubbleBuilder(BuildContext ctx, Message msg, MessageRenderContext rCtx) {
final p = msg.payload as MyStarredPayload;
return Text('β ${p.note}');
}
@override
Widget previewBuilder(BuildContext ctx, Message msg) =>
const Text('Starred note');
@override
Map<String, Object?> serialize(MessagePayload p) => (p as MyStarredPayload).toJson();
@override
MessagePayload deserialize(Map<String, Object?> json) =>
MyStarredPayload(json['note'] as String);
@override
String fallbackText(Message msg) => 'β ${(msg.payload as MyStarredPayload).note}';
}
Transport Adapters
WebSocket
final transport = WebSocketTransport(
url: 'wss://chat.example.com/ws',
conversationId: 'conv-1',
currentUserId: me.id,
);
REST (polling)
final transport = RestTransport(
baseUrl: 'https://api.example.com',
conversationId: 'conv-1',
pollInterval: const Duration(seconds: 5),
);
Firestore (stub)
Wire the FirestoreTransport stub to cloud_firestore in your host app.
The file is not exported by default; copy and adapt lib/src/transport/adapters/firestore_transport.dart.
Custom Transport
class MyTransport implements TransportAdapter {
@override String get id => 'my_transport';
@override Future<void> connect() async { /* ... */ }
@override Future<void> disconnect() async { /* ... */ }
@override Stream<TransportEvent> get events => _controller.stream;
@override Future<DeliveryStatus> sendMessage(Message m) async {
// call your API
return DeliveryStatus.ok(serverAssignedId);
}
// implement editMessage, deleteMessage, fetchHistory, etc.
}
Theming
// Built-in presets
ChatTheme.light // default
ChatTheme.dark // dark mode
ChatTheme.adaptive(context) // follows system brightness
// Custom theme
const myTheme = ChatTheme(
ownBubbleColor: Color(0xFF6200EE),
otherBubbleColor: Color(0xFFF3E5F5),
backgroundColor: Color(0xFFFAFAFA),
showAvatars: true,
showSenderNames: false,
);
// Incremental overrides
final myTheme = ChatTheme.light.copyWith(
ownBubbleColor: Colors.deepPurple,
maxBubbleWidthFactor: 0.65,
);
Command Pattern (Undo / Redo)
// Dispatch via BLoC (recommended)
bloc.add(const UndoEvent());
bloc.add(const RedoEvent());
// Check availability
if (bloc.canUndo) { /* show undo button */ }
if (bloc.canRedo) { /* show redo button */ }
// Label for undo toast
final label = bloc.undoLabel; // e.g. "Undo send"
// Direct engine access
engine.undo();
engine.redo();
Offline Mode
final bloc = ChatBloc(
initialConversation: conv,
transport: transport,
currentUserId: me.id,
config: const ChatConfig(offlineMode: true),
);
// Failed sends are enqueued in the Outbox and flushed on reconnect.
File Structure
lib/
βββ flutter_chats_ui.dart β main public barrel
βββ plugins.dart β 12 built-in plugins barrel
βββ transports.dart β transport adapters barrel
βββ src/
βββ bloc/
β βββ chat_bloc.dart
β βββ chat_event.dart
βββ cache/
β βββ cache_adapter.dart
β βββ memory_cache.dart
β βββ outbox.dart
βββ core/
β βββ attachment.dart
β βββ chat_config.dart
β βββ chat_exception.dart
β βββ chat_state.dart
β βββ chat_user.dart
β βββ conversation.dart
β βββ message.dart
β βββ reaction.dart
β βββ thread.dart
β βββ history/
β βββ command.dart
β βββ command_manager.dart
β βββ commands/
β βββ batch_command.dart
β βββ delete_message_command.dart
β βββ edit_message_command.dart
β βββ mark_read_command.dart
β βββ pin_command.dart
β βββ react_command.dart
β βββ reply_command.dart
β βββ send_message_command.dart
βββ engine/
β βββ chat_engine.dart
β βββ date_separator_inserter.dart
β βββ message_grouper.dart
β βββ search_engine.dart
β βββ unread_marker.dart
βββ integration/
β βββ debouncer.dart
β βββ encryption_adapter.dart
β βββ i18n_adapter.dart
β βββ media_adapter.dart
β βββ notification_bridge.dart
βββ messages/
β βββ message_type_plugin.dart
β βββ message_type_registry.dart
β βββ plugins/
β βββ audio_message_plugin.dart
β βββ code_plugin.dart
β βββ contact_plugin.dart
β βββ file_message_plugin.dart
β βββ image_message_plugin.dart
β βββ location_plugin.dart
β βββ poll_plugin.dart
β βββ sticker_plugin.dart
β βββ system_event_plugin.dart
β βββ text_message_plugin.dart
β βββ video_message_plugin.dart
β βββ voice_note_plugin.dart
βββ transport/
β βββ delivery_status.dart
β βββ transport_adapter.dart
β βββ transport_event.dart
β βββ transport_registry.dart
β βββ adapters/
β βββ firestore_transport.dart
β βββ memory_transport.dart
β βββ rest_transport.dart
β βββ websocket_transport.dart
βββ ui/
βββ flutter_chats_ui.dart
βββ theming/
β βββ chat_colors.dart
β βββ chat_theme.dart
β βββ chat_typography.dart
βββ widgets/
βββ chat_app_bar.dart
βββ chat_bubble.dart
βββ chat_message_list.dart
βββ chat_screen.dart
βββ emoji_picker_widget.dart
βββ error_banner_widget.dart
βββ long_press_actions_sheet.dart
βββ mention_overlay.dart
βββ message_composer.dart
βββ pinned_messages_panel.dart
βββ presence_indicator.dart
βββ scroll_to_bottom_fab.dart
βββ search_bar_widget.dart
βββ swipe_to_reply.dart
βββ thread_panel.dart
βββ typing_dots.dart
βββ unread_chip.dart
βββ waveform_widget.dart
test/
βββ chat_engine_test.dart
βββ command_manager_test.dart
βββ message_grouper_test.dart
βββ outbox_test.dart
βββ search_engine_test.dart
βββ transport_adapter_test.dart
βββ integration/
β βββ send_receive_cycle_test.dart
βββ widget/
βββ chat_bubble_test.dart
βββ chat_screen_test.dart
Requirements
| Dependency | Version |
|---|---|
| Dart SDK | >=3.3.0 <4.0.0 |
| Flutter | >=3.19.0 |
equatable |
^2.0.5 |
uuid |
^4.4.0 |
collection |
^1.18.0 |
meta |
^1.12.0 |
Contributing
- Fork the repository
- Create your feature branch:
git checkout -b feature/my-feature - Commit your changes:
git commit -m 'feat: add my feature' - Push:
git push origin feature/my-feature - Open a Pull Request
Please follow the existing code style (Clean Architecture, immutable domain models, const everywhere possible).
License
This project is licensed under the Apache License 2.0 β see LICENSE for details.
Β© 2026 timsoft-org
Libraries
- flutter_chats_ui
- plugins
- transports
- flutter_chats_ui β Transport adapters barrel.