In my flutter app, I am using the flutter background service plugin.
I share data among isolates using ports. There is a mainIsolate
present in the homepage
‘s code. Then a bgIsolate
of the background_service
. When the background_service
isolate does not hold data and needs to close, I check if the application is open or not, and do not close the background_service
isolate and the process when the application is open in case the application sends more data to it. I close it when the application is in terminated state (closed) and it automatically turns on again when the application is opened again.
Now, from the background_service
isolate, to check if the app is on or not, I send a ping
message to mainIsolate
for which it null-checks the bgIsolate
‘s sendPort
and sends back a pong
message for which I wait for 5 seconds.
In debug mode, there are no issues. The conversations between the isolate are as expected. The issues comes in release mode. bgIsolate
is able to send the ping
message to the mainIsolate
but when mainIsolate
null-checks the bgIsolate
‘s sendPort
, it returns false
.
But when I try to send messages from other parts of the app. Like the database section which sends updates to the bgIsolate
, I can see that the updates are still being sent without issues, even in the release mode. The database is not in any other isolate though, it is still in the mainIsolate
the difference is only in the file from where the data is being sent. The pong
message is being sent from the homepage
while the data updates are being sent from the database file.
The problem is only with sending messages from the mainIsolate
code present in homepage
.
Now I would share some code with you:
Note: The bg_isolate_name
is a variable stored in a different file which contains the name of the bgIsolate
.
This is the background_service
file. I removed parts unrelated to this matter.
// imports
@pragma('vm:entry-point')
bool mainIsActive = false;
Map<int, Reminder> reminders = {};
final List<Reminder> activeStatusReminders = [];
final Map<int, int> repeatNumbers = {};
final ReceivePort receivePort = ReceivePort();
Future<void> initializeService() async {
final service = FlutterBackgroundService();
await service.configure(
iosConfiguration: IosConfiguration(),
androidConfiguration: AndroidConfiguration(
autoStart: true,
onStart: onStart,
isForegroundMode: true
)
);
service.startService();
}
@pragma('vm:entry-point')
void onStop(ServiceInstance service) async {
debugPrint("[BGS onStop] called | ${service.runtimeType}");
receivePort.close();
IsolateNameServer.removePortNameMapping(bg_isolate_name);
await service.stopSelf();
debugPrint("[BGS onStop] service stopped");
}
@pragma('vm:entry-point')
void onStart(ServiceInstance service) async {
// Control Section
if (service is AndroidServiceInstance) {
service.on('setAsForeground').listen((event) {
service.setAsForegroundService();
});
service.on('setAsBackground').listen((event) {
service.setAsBackgroundService();
});
}
service.on('stopService').listen((event) {
onStop(service);
});
//
if (service is AndroidServiceInstance)
{
service.setForegroundNotificationInfo(
title: "On Standby",
content: "Will disappear automatically if not needed."
);
}
startListners(service);
// Life Section
final now = DateTime.now();
final Duration delay = DateTime(now.year, now.month, now.day, now.hour, now.minute + 1)
.difference(now);
debugPrint("[BBGGSS] delay: ${delay.inSeconds}");
Timer.periodic(Duration(seconds: delay.inSeconds), (timerFirst) async { // Double Timers are used to sync the Timer with 00 seconds. Like, run only on 5min 0 seconds and not on 5 min 5 seconds.
bgServicePeriodicWork(service);
Timer.periodic(Duration(minutes: 1), (timer) {
bgServicePeriodicWork(service);
});
timerFirst.cancel();
});
}
Future<void> bgServicePeriodicWork(ServiceInstance service) async {
// More code. Not related to issue.
// This holds the things which the background service repeatedly does.
}
@pragma('vm:entry-point')
Future<bool> stopBackgroundService(
AndroidServiceInstance service
) async {
debugPrint("[BGS] Attempting to stop background service");
final SendPort? mainIsolate = IsolateNameServer.lookupPortByName('main');
if (mainIsolate == null) {
debugPrint("[BGS] mainIsolate is null");
onStop(service);
return true;
} else {
debugPrint("[BGS] mainIsolate found");
mainIsActive = false;
mainIsolate.send('ping_from_bgIsolate');
// send message and wait. The listener handles received message and changes value of mainIsActive variable.
debugPrint("[BGS] sent ping_from_bgIsolate");
await Future.delayed(Duration(seconds: 5));
if (mainIsActive)
{
debugPrint("[BGS] mainIsolate is active");
return false;
}
else
{
debugPrint("[BGS] mainIsolate is not active");
onStop(service);
return true;
}
}
}
@pragma('vm:entry-point')
void updateNotification(AndroidServiceInstance service) async{
// Function reads the data and updates the notification. It also calls for the service to stop in a certain scenario.
}
@pragma('vm:entry-point')
void startListners(
ServiceInstance service
) {
IsolateNameServer.registerPortWithName(receivePort.sendPort, bg_isolate_name);
// Listening for reminders
receivePort.listen((dynamic message) {
debugPrint("[BGS] Message nnnnnReceived $message");
if (message is Map<int, Map<String, dynamic>>) { // Received Reminders from Hive DB
handleReceivedRemindersData(message);
if (service is AndroidServiceInstance) updateNotification(service);
}
else if (message is Map<String, String>) // Received update after button click on notification. id and action.
{
handleNotificationButtonClickUpdate(message);
if (service is AndroidServiceInstance) updateNotification(service);
}
else if (message is String) // String messages, ping, pong.
{
debugPrint("[BGS] Message: $message");
if (message == 'pong')
{
mainIsActive = true;
debugPrint("[BGS Listener] mainIsActive: $mainIsActive");
}
// else debugPrint("[BGS] Unknown Content $message");
}
else {
// debugPrint("[BGS] Unknown Content $message");
}
});
}
@pragma('vm:entry-point')
void handleReceivedRemindersData(Map<int, Map<String, dynamic>> message) {
// Handles the data received from the database file.
}
@pragma('vm:entry-point')
void handleNotificationButtonClickUpdate(Map<String, String> message) {
// Handles when a notification button is clicked. Not the notification of background_service, but of apps. The application is focused around notification and the background_service helps with it.
}
Here is the homepage code related to isolate transaction,
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
final ReceivePort receivePort = ReceivePort();
final bgIsolate = IsolateNameServer.lookupPortByName(bg_isolate_name);
@override
void initState() {
super.initState();
if (bgIsolate != null)
{
bgIsolate!.send("main_is_active");
}
else debugPrint("[homepageInit] bgIsolate is down");
// Listening for reloading orderers
IsolateNameServer.registerPortWithName(receivePort.sendPort, 'main');
receivePort.listen((dynamic message) {
if (message == 'ping_from_bgIsolate')
{
debugPrint("[homepageListener] received ping_from_bgIsolate");
if (bgIsolate != null) // Initialized on top
{
bgIsolate!.send("pong");
}
else debugPrint("[homePageListener] bgIsolate is null");
debugPrint("[homepageListener] sent back pong to bgIsolate");
}
else
{
debugPrint("[homepageListener] Unknown string received");
}
}
else
{
debugPrint("[homepageListener] Unknown message received $message");
}
});
}
.
.
// More code
.
.
}
Here is code from the database file which is always working unlike the homepage file.
static void updateReminders() async
// more code
final SendPort? backgroundIsolate = IsolateNameServer.lookupPortByName(bg_isolate_name);
if (backgroundIsolate != null)
{
debugPrint("[updateReminders] message sending");
final message = RemindersDatabaseController.getRemindersAsMaps();
backgroundIsolate.send(message);
}
else
{
debugPrint("[updateReminders] background Isolate is null");
}
}
I haven’t tried much in this issue because I just don’t know what to do. A general idea of what might be causing this could help me.
I know this much that something is getting wrong in the release mode optimisations. But not how I could fix this or even what is getting wrong.