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.
After some poking around, I found out that ReceivedAction
passed to onActionReceivedMethod
actually contain the NotificationContent
as a Map
, solving my problem.