BarrageStore module in the AtomicXCore framework to quickly add a robust, high-performance barrage (live chat overlay) system to your live streaming app.
BarrageStore delivers a complete live comment solution for live streaming apps. Key features include:Core Concept | Type | Core Responsibility and Description |
class | Represents a data model for a live comments. It contains all key information such as sender information ( sender), message content (textContent or data), and message type (messageType). | |
class | Represents the current status of the live comment module. Its core attribute messageList is a ValueListenable<List<Barrage>>, storing all live comments in the live streaming room in chronological order, serving as the data source for UI rendering. | |
class | The central manager for live comments. Use it to send messages ( sendTextMessage, sendCustomMessage) and listen to its barrageState.messageList property to receive and track all live comments updates. |
BarrageStore instance bound to the current live room's liveId, and set up a listener to receive real-time updates of the complete live comment list.import 'package:flutter/foundation.dart';import 'package:atomic_x_core/atomicxcore.dart';class BarrageManager {final String liveId;late final BarrageStore barrageStore;late final VoidCallback _messageListChangedListener = _onMessageListChanged;// Expose message list change notification to the publicfinal ValueNotifier<List<Barrage>> messagesNotifier =ValueNotifier<List<Barrage>>([]);BarrageManager({required this.liveId}) {// 1. Get the singleton of BarrageStore by liveId (location parameter)barrageStore = BarrageStore.create(liveId);// 2. Start listening to the bullet screen immediately after initialization_subscribeToBarrageUpdates();}void _subscribeToBarrageUpdates() {// 3. Use ValueListenable's addListener to listen for messages list adjustmentbarrageStore.barrageState.messageList.addListener(_messageListChangedListener);}void _onMessageListChanged() {// 4. When the messageList is updating, get the new list and notify the UI layer// Key point: The complete list obtained here contains ALL historical messagesmessagesNotifier.value = barrageStore.barrageState.messageList.value;}void dispose() {barrageStore.barrageState.messageList.removeListener(_messageListChangedListener);messagesNotifier.dispose();}}
sendTextMessage method to broadcast a live comment to all users in the live streaming room.extension BarrageManagerSend on BarrageManager {Send a text bullet screenFuture<void> sendTextMessage(String text) async {// Recommendation: Add non-null verification to avoid sending invalid messages.if (text.isEmpty) {debugPrint("Bullet screen content cannot be empty");return;}// Call this API to send a messagefinal result = await barrageStore.sendTextMessage(text: text,extensionInfo: null,);// Use isSuccess to check the resultif (result.isSuccess) {debugPrint("Bullet screen text '$text' sent successfully");} else {debugPrint("Bullet screen text sending failure: ${result.errorMessage}");}}}
Parameter | Type | Description |
text | String | The text content to be sent. |
extensionInfo | Map<String, String>? | Additional extension info. |
import 'dart:convert';extension BarrageManagerCustom on BarrageManager {/// Send a custom barrage item, for example for sending a giftFuture<void> sendGiftMessage({required String giftId,required int giftCount,}) async {// 1. Define a business IDconst businessID = "live_gift";// 2. Encode business data as a JSON stringfinal giftData = {"gift_id": giftId, "gift_count": giftCount};final jsonString = jsonEncode(giftData);// 3. Call this API to send custom messagesfinal result = await barrageStore.sendCustomMessage(businessID: businessID,data: jsonString,);if (result.isSuccess) {debugPrint("Gift message (custom barrage item) sent successfully");} else {debugPrint("Gift message send fail: ${result.errorMessage}");}}}
Parameter | Type | Description |
businessID | String | Unique business identifier (e.g., "live_gift") to distinguish custom messages. |
data | String | Business data, typically a JSON-formatted string. |
extension BarrageManagerLocal on BarrageManager {Insert a welcome notification into the local message listvoid showWelcomeMessage(LiveUserInfo user) {// 1. Create a Barrage message (using named parameters construct function)final welcomeTip = Barrage(messageType: BarrageType.text,Welcome ${user.userName} to the live room.);// 2. Call this API to insert it into the local listbarrageStore.appendLocalTip(welcomeTip);}}
disableSendMessage API in LiveAudienceStore.// 1. Get the LiveAudienceStore instance bound to the current live streaming room (location parameter)final liveId = "your_live_id";final audienceStore = LiveAudienceStore.create(liveId);// 2. Define the operating user ID and mute statusfinal userIdToMute = "user_id_to_be_muted";final shouldDisable = true; // true for mute, false for unblock// 3. Call API to perform operationfinal result = await audienceStore.disableSendMessage(userID: userIdToMute,isDisable: shouldDisable,);if (result.isSuccess) {debugPrint("${shouldDisable ? "mute" : "unblock"} user $userIdToMute successfully");} else {debugPrint("Operation failure: ${result.errorMessage}");}
LiveListStore.// 1. Get the LiveListStore singletonfinal liveListStore = LiveListStore.shared;// 2. Get the current live room information, create a new LiveInfo object, and modify the global mute statusfinal currentLiveInfo = liveListStore.liveState.currentLive.value;final updatedLiveInfo = LiveInfo(liveID: currentLiveInfo.liveID,liveName: currentLiveInfo.liveName,isMessageDisable: true, // true for mute all, false for shutdown);// 3. Call the update API and specify the modify flags listfinal result = await liveListStore.updateLiveInfo(liveInfo: updatedLiveInfo,modifyFlagList: [ModifyFlag.isMessageDisable],);if (result.isSuccess) {debugPrint("Global mute status updated successfully");} else {debugPrint("Operation failure: ${result.errorMessage}");}
import 'package:flutter/material.dart';import 'package:atomic_x_core/atomicxcore.dart';class BarrageWidget extends StatefulWidget {final String liveId;const BarrageWidget({Key? key, required this.liveId}) : super(key: key);@overrideState<BarrageWidget> createState() => _BarrageWidgetState();}class _BarrageWidgetState extends State<BarrageWidget> {late BarrageManager _barrageManager;final TextEditingController _inputController = TextEditingController();final ScrollController _scrollController = ScrollController();@overridevoid initState() {super.initState();_barrageManager = BarrageManager(liveId: widget.liveId);}void _scrollToBottom() {WidgetsBinding.instance.addPostFrameCallback((_) {if (_scrollController.hasClients) {_scrollController.animateTo(_scrollController.position.maxScrollExtent,duration: const Duration(milliseconds: 200),curve: Curves.easeOut,);}});}@overridevoid dispose() {_barrageManager.dispose();_inputController.dispose();_scrollController.dispose();super.dispose();}@overrideWidget build(BuildContext context) {return Column(children: [// Bullet screen listExpanded(child: ValueListenableBuilder<List<Barrage>>(valueListenable: _barrageManager.messagesNotifier,builder: (context, messageList, child) {// Scroll to the bottom_scrollToBottom();return ListView.builder(controller: _scrollController,itemCount: messageList.length,itemBuilder: (context, index) {final barrage = messageList[index];return _buildBarrageItem(barrage);},);},),),// input box_buildInputBar(),],);}Widget _buildBarrageItem(Barrage barrage) {return Container(margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),decoration: BoxDecoration(color: Colors.black54,borderRadius: BorderRadius.circular(16),),child: RichText(text: TextSpan(children: [TextSpan(text: '${barrage.sender.userName}: ',style: const TextStyle(color: Colors.yellow,fontSize: 14,fontWeight: FontWeight.bold,),),TextSpan(text: barrage.textContent,style: const TextStyle(color: Colors.white, fontSize: 14),),],),),);}Widget _buildInputBar() {return Container(padding: const EdgeInsets.all(8),color: Colors.white,child: Row(children: [Expanded(child: TextField(controller: _inputController,decoration: const InputDecoration(hintText: 'Say something...',border: OutlineInputBorder(),contentPadding: EdgeInsets.symmetric(horizontal: 12),),onSubmitted: (_) => _sendMessage(),),),const SizedBox(width: 8),ElevatedButton(onPressed: _sendMessage,child: const Text('Send')),],),);}void _sendMessage() {final text = _inputController.text.trim();if (text.isNotEmpty) {_barrageManager.sendTextMessage(text);_inputController.clear();}}}
BarrageStore, you may need to handle more demanding scenarios to ensure a smooth and stable user experience, especially during high-concurrency live streams. The following use cases provide practical optimization strategies and code samples.class BarrageUIManager {List<Barrage>? _latestMessageList;Timer? _refreshTimer;final void Function(List<Barrage>) onUpdate;BarrageUIManager({required this.onUpdate}) {// Check whether need to refresh every 0.3 seconds_refreshTimer = Timer.periodic(const Duration(milliseconds: 300),(_) => _refreshUIIfNeeded(),);}/// This method is frequently invoked externally with the latest full listvoid update(List<Barrage> fullList) {_latestMessageList = fullList;}void _refreshUIIfNeeded() {// Check whether there is new data pending refreshfinal newList = _latestMessageList;if (newList == null) return;_latestMessageList = null; // Clear flags to avoid repeated refresh// Update data source and refresh UIonUpdate(newList);}void dispose() {_refreshTimer?.cancel();}}
messageList grows indefinitely during long streams. Even with throttled UI updates, the underlying data array can consume excessive memory, eventually leading to crashvoid _refreshUIIfNeeded() {final fullList = _latestMessageList;if (fullList == null) return;_latestMessageList = null;// Key point: Take the latest N messagesconst capacity = 500; // The client keeps the latest 500 messages onlyfinal cappedList = fullList.length > capacity? fullList.sublist(fullList.length - capacity): fullList;onUpdate(cappedList);}
sendCustomMessage. BarrageStore gives you full flexibility for business-specific live comments.{ "type": "colored_text", "text": "This is a color bullet screen!", "color": "#FF5733" }
data parameter of sendCustomMessage. businessId can be set to a unique identifier representing your business, such as "barrage_style_v1".messageType is .custom and whether businessId matches. If matched, parse the data string (usually parsing JSON), and render your custom UI style based on the parsed data (such as color, text).AtomicXCore ensures that as long as you use the same liveId, you always get the same BarrageStore instance for that live room. You don't need to manage the singleton yourself.AtomicXCore supports pulling history live comments, but this requires you to perform one simple configuration in the server console. Once configured, the SDK will automatically handle everything subsequently with no need to write additional code.
AtomicXCore automatically retrieves the configured number of historical messages at the SDK level. These messages are delivered to your UI via the same v subscription you already use. Your app will receive and display these historical barrages just like real-time messages.Feedback