this is my bloc code
i want to create functionality of real time chat with Laravel API, pusher, and with one signal. All of my code works correctly except when i send new message to chat user, user receives my message and I'm able to print the message and data but recipient user's UI is not updating for new message.
import 'package:flutter/material.dart';
import 'package:soulsphere/bloc/chat/chat_message_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:soulsphere/repositories/chats/chat_messages_repo.dart';
import 'package:soulsphere/model/chat_message.dart';
class ChatMessageCubit extends Cubit<ChatMessageState>{
final BuildContext context;
final String chatUserID;
ChatMessageCubit({
required this.context,
required this.chatUserID,
}) : super(ChatMessageLoadingState()){
fetchChatMessage(context, chatUserID);
}
ChatMessageRepository chatMessageRepository = ChatMessageRepository();
void fetchChatMessage(BuildContext context, String? chatUserID) async{
print("========================================");
print("in fetch chat message api");
try{
print("fetch message for: $chatUserID");
if(chatUserID != 0){
List<ChatMessage> chatMessages = await chatMessageRepository.fetchChatsFromApi(context, chatUserID);
emit(ChatMessageLoadedState(chatMessages));
}
else{
emit(ChatMessageErrorState("No Chat User Found"));
}
}
catch (ex){
emit(ChatMessageErrorState(ex.toString()));
}
}
void sendMessage(BuildContext context, String message, String chatUserID) async {
try{
List<ChatMessage> sentMessage = (await chatMessageRepository.sendMessage(context, message, chatUserID)).cast<ChatMessage>();
emit(ChatMessageLoadedState(sentMessage));
}
catch (ex){
emit(ChatMessageErrorState(ex.toString()));
}
}
void addNewMessageFromPusher(Map<String, dynamic> messageData) {
print("in add new message from pusher");
if (state is ChatMessageLoadedState) {
ChatMessage newMessage = ChatMessage.fromJson(messageData);
List<ChatMessage> currentMessages = List.from((state as ChatMessageLoadedState).chatMessages);
currentMessages.add(newMessage);
print("============================================================");
print("============================================================");
emit(ChatMessageLoadedState(currentMessages));
}
}
}
and my chat screen code
import 'package:flutter/material.dart';
import 'package:pusher_client/pusher_client.dart';
import 'package:soulsphere/controller/laravel_echo/laravel_echo.dart';
import 'package:soulsphere/screens/users/user_view.dart';
import 'package:soulsphere/utils/app_constants.dart';
import 'package:soulsphere/model/user.dart';
import 'package:soulsphere/model/chat_message.dart';
import 'package:shimmer/shimmer.dart';
import 'dart:convert';
import 'package:soulsphere/utils/show_toast.dart';
import 'package:soulsphere/utils/shared_pref.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:soulsphere/bloc/chat/chat_message_cubit.dart';
import 'package:soulsphere/bloc/chat/chat_message_state.dart';
class ChatDetailsScreen extends StatefulWidget {
final User user;
const ChatDetailsScreen({
Key? key,
required this.user,
}) : super(key: key);
@override
State<ChatDetailsScreen> createState() => _ChatDetailsScreenState();
}
class _ChatDetailsScreenState extends State<ChatDetailsScreen> {
bool isLoading = true;
late String userID;
bool isChatDataFetched = true;
late ChatMessageCubit chatMessageCubit;
void listenChatChannel(chatID) {
LaravelEcho.instance.channel('chat.$chatID').listen(
'.message.sent',
(e) {
if (e is PusherEvent) {
if (e.data != null) {
Map<String, dynamic> messageData = json.decode(e.data!);
print("in function listen channel");
print(messageData);
chatMessageCubit.addNewMessageFromPusher(messageData);
}
}
},
).error((err) {
showToast(context, 'Pusher error: $err');
});
}
void leaveChatChannel(chatID){
try{
LaravelEcho.instance.leave('chat.$chatID');
}
catch(err){
showToast(context, err.toString());
}
}
@override
void initState() {
super.initState();
getUserDataFromSharedPreferences();
String chatID = widget.user.chatID.toString();
// Initialize chatMessageCubit before using it in listenChatChannel
chatMessageCubit = ChatMessageCubit(context: context, chatUserID: widget.user.userID.toString());
listenChatChannel(chatID);
}
@override
void dispose() {
String chatID = widget.user.chatID.toString();
leaveChatChannel(chatID);
chatMessageCubit.close();
super.dispose();
}
// ------- make UserID variable for using in build ------- //
Future<void> getUserDataFromSharedPreferences() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
userID = prefs.getString(CustomSharedConstants.userID) ?? '';
});
}
@override
Widget build(BuildContext context) {
User user = widget.user;
return BlocProvider(
create: (context) => ChatMessageCubit(context: context, chatUserID: widget.user.userID.toString()),
child: Scaffold(
appBar: AppBar(
title: Text(user.userName ?? AppConstants.noUserName),
backgroundColor: Colors.transparent,
flexibleSpace: Container(
decoration: const BoxDecoration(
gradient: AppColors.bgColorGradient,
),
),
actions: [
PopupMenuButton(
itemBuilder: (context) {
return [
const PopupMenuItem(
value: 'profile',
child: Text('View Profile'),
),
const PopupMenuItem(
value: 'settings',
child: Text('Settings'),
),
];
},
onSelected: (value){
if(value == 'profile'){
Navigator.push(context, MaterialPageRoute(builder: (context) => UserProfileScreen(user: user)));
}
},
),
],
),
body: BlocBuilder<ChatMessageCubit, ChatMessageState>(
key: const Key('chatMessagesKey'),
builder: (context, state) {
if(state is ChatMessageLoadingState){
print("in loading state");
return const Center(child: CircularProgressIndicator());
}
else if (state is ChatMessageLoadedState){
print("in loaded state");
return Column(
children: [
Expanded(
child: SingleChildScrollView(
child: ChatMessages(messages: state.chatMessages, userId: userID),
),
),
ChatInputField(
onSendMessage: (message, chatUserID) {
context.read<ChatMessageCubit>().sendMessage(context, message, chatUserID);
},
chatUserID: widget.user.userID.toString(),
onUpdateChatMessages: (updatedMessages) {
// Implement the logic to update messages if needed
},
),
],
);
}
else if(state is ChatMessageErrorState){
return Center(child: Text('Error: ${state.error}'));
}
else{
return const Center(child: Text('Failed to load data from API'));
}
},
),
),
);
}
}
class ShimmerEffect extends StatelessWidget {
const ShimmerEffect({super.key});
@override
Widget build(BuildContext context) {
return Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Container(
width: 200.0,
height: 20.0,
color: Colors.white,
),
);
},
),
);
}
}
class ChatMessages extends StatelessWidget {
final List<ChatMessage> messages;
final String userId;
const ChatMessages({
Key? key,
required this.messages,
required this.userId,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: messages.length,
itemBuilder: (BuildContext context, int index) {
return ChatBubble(
message: messages[index].messageText!,
isMe: messages[index].userID.toString() == userId,
);
},
);
}
}
class ChatBubble extends StatelessWidget {
final String message;
final bool isMe;
const ChatBubble({
Key? key,
required this.message,
required this.isMe,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Align(
alignment: isMe ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
margin: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: isMe ? Colors.blue : Colors.grey[300],
borderRadius: BorderRadius.circular(10.0),
),
child: Text(
message,
style: TextStyle(color: isMe ? Colors.white : Colors.black),
),
),
);
}
}
class ChatInputField extends StatefulWidget {
final Function(String, String) onSendMessage;
final String chatUserID;
final Function(List<ChatMessage>) onUpdateChatMessages;
const ChatInputField({
Key? key,
required this.onSendMessage,
required this.chatUserID,
required this.onUpdateChatMessages,
}) : super(key: key);
@override
State<ChatInputField> createState() => _ChatInputFieldState();
}
class _ChatInputFieldState extends State<ChatInputField> {
bool _isSending = false;
final _messageController = TextEditingController();
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
IconButton(
icon: const Icon(Icons.attach_file),
onPressed: () {
// Handle attachment button press
},
),
Expanded(
child: TextFormField(
controller: _messageController,
decoration: const InputDecoration(
contentPadding: EdgeInsets.all(15.0),
hintText: 'Type a message...',
border: InputBorder.none,
),
),
),
IconButton(
icon: _isSending
? const CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
)
: const Icon(Icons.send),
onPressed: _isSending
? null
: () async {
setState(() {
_isSending = true;
});
String message = _messageController.text;
await widget.onSendMessage(message, widget.chatUserID);
setState(() {
_isSending = false;
_messageController.clear();
});
},
),
],
),
);
}
}
i have tried with changes a lot in codes.
void listenChatChannel(chatID) {
LaravelEcho.instance.channel('chat.$chatID').listen(
'.message.sent',
(e) {
if (e is PusherEvent) {
if (e.data != null) {
Map<String, dynamic> messageData = json.decode(e.data!);
print("in function listen channel");
print(messageData);
chatMessageCubit.addNewMessageFromPusher(messageData);
}
}
},
).error((err) {
showToast(context, 'Pusher error: $err');
});
}
this code is triggered when new message received by target user
You are using different instances of
ChatMessageCubit
.you can pass the same instance of ChatMessageCubit to all the BlocBuilders. You are creating a chatMessageCubit instance in you initState.just pass the same instance to the BlocBuilders. sample code is given below.