flutterdartdart-isolatesawesome-notifications

How can I keep a variable value inside an isolate/FlutterBackgroundService?


I'm currently working on implementing an isolate with a notification handler in Flutter.

It's already working, I have a MQTT listener that shows a notification using AwesomeNotification with 2 actions for accepting or rejecting the notification.

When a notification is accepted, I want to make an API call to update the record related to that notification.

To make it a bit easy for me, I included some details with the MQTT payload that I need to use to make the API call, I set that detail to a variable so I can use it later on.

The problem is that when the app reaches AwesomeNotification's onActionReceivedMethod, the variable's value is already gone.

I think the reason for this is because, down the line, the executing code is already outside the scope of the service/isolate so the variable can't be reached already.

I then tried to use SharedPreferences, but a key doesn't get updated as fast as I wanted/expected it to be. Now, I'm at a loss as to how I could keep a bit of value around so I could proceed with the process.

Here's my current code:

import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:isolate';
import 'dart:ui';

import 'package:baraco_frontend/helpers/api/request_vehicle_change_api_helper.dart';
import 'package:baraco_frontend/helpers/sharedPref/shared_pref_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:intl/intl.dart';
import 'package:mqtt_client/mqtt_client.dart';
import 'package:vibration/vibration.dart';
import 'package:awesome_notifications/awesome_notifications.dart';

import '../globals.dart';
import '../models/user.dart';
import 'mqtt_helper.dart';

final _service = FlutterBackgroundService();
String notifTxt = 'Waiting for notification';
const String approveKey = 'approve';
const String rejectKey = 'reject';
const String approveLabel = 'Approve';
const String rejectLabel = 'Reject';
String _requestVehicleChangeId = '';
ReceivePort processRequestVehicleChangeRecvPort = ReceivePort('processRequestVehicleChangeRecv');

Future<void> initializeService() async{
  log('initializeService');
  await _service.configure(
    androidConfiguration: AndroidConfiguration(
      autoStart: true,
      onStart: _onServiceStart,
      isForegroundMode: true,
    ),
    iosConfiguration: IosConfiguration(
      autoStart: true,
      onForeground: _onServiceStart,
      onBackground: _onServiceIosBackground,
    ),
  );

  await initializeLocalNotifications();
  await initializeIsolateReceivePort();
  startListeningNotificationEvents();
}

@pragma('vm:entry-point')
Future<void> _onServiceStart(ServiceInstance service) async {
  DartPluginRegistrant.ensureInitialized();
  log('service started');

  if (service is AndroidServiceInstance) {
    service.on('setAsForeground').listen((ev) {
      service.setAsForegroundService();
    });
    service.on('setAsBackground').listen((ev) {
      service.setAsBackgroundService();
    });
  }

  service.on('stopService').listen((ev) {
    service.stopSelf();
  });

  MqttHelper().connect().then((val) async {
    var user = User.fromJson( jsonDecode(await SharedPrefHelper().getPrefWithKey( Globals.USER_PREF_KEY )) );

    MqttHelper().send('hi from the app', user.mqttClientId);
    MqttHelper().subscribe(user.mqttClientId);
  });

  Timer(const Duration(milliseconds: 1000), () {
    MqttHelper().getClient().updates!.listen((List<MqttReceivedMessage<MqttMessage?>>? c) async {
      final recMessage = c![0].payload as MqttPublishMessage;
      final payload = MqttPublishPayload.bytesToStringAsString(recMessage.payload.message);
      var payloadArr = payload.split(':');
      log('payloadArr: $payloadArr');
      notifTxt = payloadArr[0];
      _requestVehicleChangeId = payloadArr[1];
      
      // _requestVehicleChangeId surely has value here
      

      showNotification('BARACO Notification', notifTxt);
      Vibration.vibrate(pattern: [0,
        550, 100,
        550, 100,
        550, 100,
        550, 100,
        200, 50,
        200, 50,
        200, 50,
        200, 190, 700]);
    });
  });
}

@pragma('vm:entry-point')
Future<bool> _onServiceIosBackground(ServiceInstance service) async{
  WidgetsFlutterBinding.ensureInitialized();
  DartPluginRegistrant.ensureInitialized();
  log('onServiceIosBackground started');
  return false;
}

Future<void> initializeLocalNotifications() async {
  await AwesomeNotifications().initialize(
    null,
    [
    NotificationChannel(
      channelKey: 'alerts',
      channelName: 'Alerts',
      channelDescription: 'Notification tests as alerts',
      playSound: true,
      onlyAlertOnce: true,
      importance: NotificationImportance.High,
      defaultPrivacy: NotificationPrivacy.Private,
      defaultColor: Colors.deepPurple,
      ledColor: Colors.deepPurple
    )
    ],
    debug: true
  );
}

ReceivePort? receivePort;
Future<void> initializeIsolateReceivePort() async {
  receivePort = ReceivePort('Notification action port in main isolate')
  ..listen(
    (silentData) {
      onActionReceived(silentData);
    }
  );

  IsolateNameServer.registerPortWithName(
  receivePort!.sendPort, 'notification_action_port');
  log('initializeIsolateReceivePort');
}

Future<void> startListeningNotificationEvents() async {
  AwesomeNotifications()
    .setListeners(onActionReceivedMethod: onActionReceived);
}

@pragma('vm:entry-point')
Future<void> onActionReceived(ReceivedAction receivedAction) async {
    // _requestVehicleChangeId has no value now here
    // this is where I wanted to do the api calls
  
}

@pragma('vm:entry-point')
Future<void> showNotification(String title, String body) async {
  // _requestVehicleChangeId still has value here

  bool isAllowed = await AwesomeNotifications().isNotificationAllowed();

  await AwesomeNotifications().createNotification(
      content: NotificationContent(
          id: -1,
          channelKey: 'alerts',
          title: title,
          body: body,
          bigPicture: '',
          largeIcon: '',
          notificationLayout: NotificationLayout.BigPicture,
          payload: {'notificationId': '1234567890'}),
      actionButtons: [
        NotificationActionButton(
            key: rejectKey,
            label: rejectLabel,
            actionType: ActionType.SilentAction,
            isDangerousOption: true),
        NotificationActionButton(
            key: approveKey,
            label: approveLabel,
            actionType: ActionType.SilentAction)
      ]
  );
}

UPDATE:

I just realized that AwesomeNotification have this payload field called content which has a field called payload which seems to be accept a Map but you can't seem to access it even in their own callbacks. The examples filled payload with something, but weren't used. How weird.


Solution

  • After some poking around, I found out that ReceivedAction passed to onActionReceivedMethod actually contain the NotificationContent as a Map, solving my problem.