I am building a chat section in my app. I was finishing the handling of data messages sent from the server to the devices when I accidentally stumped upon a serious problem where firebase was opening a new isolate to run background tasks. I use GetX controller ChatController
which has a chats
variable of type RxList<Chat>
. Each chat has a messages list. normally when someone sends a message and the device is in foreground, I take the new message, do some serialization here and there, and add it to its corresponding chat in the ChatController
‘s chats
. When doing same thing in background, but it just reinitializes a new one with no data at all which stops the process of adding a new message.
I tried making the chat controller permanent but didn’t work. How can I make firebase’s background isolate utilize the existing chat controller? even if the user dragged from top to see notifications, I can’t update chats
. This is really absurd.
if (Get.isRegistered<ChatController>()) {
// If the controller is already registered, reuse it
chatController = Get.find<ChatController>();
print("Chat Controller Found");
} else {
// If the controller is not registered, initialize it
chatController = Get.put(ChatController());
await chatController
.getChats(); // Only fetch chats on the first initialization
print("Chat Controller Created!");
}
chatController.addMessageToChat(
int.parse(message.data["chat_id"]),
chatMessage,
);
ChatController.dart:
class ChatController extends GetxController with WidgetsBindingObserver {
final DioClient _dioClient = DioClient();
RxInt currentChat = (-1).obs;
RxList<Chat> chats = <Chat>[].obs;
RxBool isInBackground = false.obs;
RxList<ChatMessage> backgroundMessages = <ChatMessage>[].obs;
RxList<ChatMessage> chatMessages = <ChatMessage>[].obs;
@override
void onInit() {
super.onInit();
getChats();
WidgetsBinding.instance.addObserver(this); // Add observer for app lifecycle
}
@override
void onClose() {
WidgetsBinding.instance
.removeObserver(this); // Remove observer when controller is destroyed
super.onClose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
// App comes back to foreground, refresh data
isInBackground.value = false;
for (ChatMessage message in backgroundMessages) {
print("Message added from backgroundMessages");
addMessageToChat(message.chatId, message);
}
backgroundMessages.clear();
} else if (state == AppLifecycleState.paused) {
isInBackground.value = true;
}
print("isInBackground from life cycle: ${isInBackground.value}");
}
void addMessageToChat(int chatId, ChatMessage message) {
print("isInBackground from add message: ${isInBackground.value}");
// if (isInBackground.value) {
// backgroundMessages.add(message);
// print("Message added to backgroundMessages");
// return;
// } else {
// print("Message added to chatMessages");
// }
// Find the chat
Chat chat = chats.firstWhere((chat) => chat.id == chatId);
if (chat.id != -1) {
chat.messages.add(message);
Map messageMap = message.toMap();
messageMap['created_at'] = formatMessageDate(
message.createdAt.toLocal().toIso8601String(),
Global.activeUser!.timezone ?? "UTC");
messageMap['sender']['name'] =
messageMap['sender']['member_id'] == chat.userId
? "You"
: messageMap['sender']['name'];
if (messageMap['sender']['member_id'] != chat.userId) {
chat.counter = currentChat.value != chatId ? chat.counter + 1 : 0;
}
chat.lastMessageMap = messageMap;
// Update the list by reordering the chat without rebuilding the entire list
int currentIndex = chats.indexOf(chat);
if (currentIndex != 0) {
chats.removeAt(currentIndex);
chats.insert(0, chat);
chats.refresh(); // This will trigger a partial rebuild
}
}
}
2
Answers
What I needed to do was to create another isolate in the main isolate to communicate using
ReceiverPort()
andSendPort()
between FCM isolate and main isolate.the new isolate:
You will need to call startReceivePort() before
runApp(const MyApp())
inmain.dart
andstopReceivePort()
in thedispose()
in your main stateful widget of your app and voila!In my experience, the easiest way is to use a database;
You must consider two scenarios:
that the application is completely closed and the Firebase system starts your operations for a few seconds, at which time it would save information in the database.
That the application is in the background but the system still keeps it alive, in that case I would use the WidgetsBindingObserver to synchronize the information that is in the database with what you need.
My recommendation is that you use Hive, it is simple and for a synchronization task like this I have used it and it has worked well for me.
Keep in mind that in the Background an isolate cannot modify UI components.