I'm working on an app that receives notifications from firebase cloud messaging. I save the message in Hive upon receipt. I have a notification screen that displays the notification read from hive which updates immediately when notification is received. This has been working well.
The problem now is, notification received when the app is running in background (not kill/terminate) is saved in hive but screen is not updated when navigated to notification screen (the update in the hive is not seen) until the app is terminated and reran.
I read this is because onBackgroundMessage handler runs on different isolate and isolate cannot share storage. It seems like I need a way to pass the Hive notification update to the main isolate from the onBackgroundMessage handler.
This is my implementation so far
push_message.dart the notification class whose instance is save in hive
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:app_name/helpers/helpers.dart';
part 'push_message.g.dart';
@HiveType(typeId: 1)
class PushMessage extends HiveObject {
@HiveField(0)
int id = int.parse(generateRandomNumber(7));
@HiveField(1)
String? messageId;
@HiveField(2)
String title;
@HiveField(3)
String body;
@HiveField(4)
String? bigPicture;
@HiveField(5)
DateTime? sentAt;
@HiveField(6)
DateTime? receivedAt;
@HiveField(7)
String? payload;
@HiveField(8, defaultValue: '')
String channelId = 'channel_id';
@HiveField(9, defaultValue: 'channel name')
String channelName = 'channel name';
@HiveField(10, defaultValue: 'App notification')
String channelDescription = 'App notification';
PushMessage({
this.id = 0,
this.messageId,
required this.title,
required this.body,
this.payload,
this.channelDescription = 'App notification',
this.channelName = 'channel name',
this.channelId = 'channel_id',
this.bigPicture,
this.sentAt,
this.receivedAt,
});
Future<void> display() async {
AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
channelId,
channelName,
channelDescription: channelDescription,
importance: Importance.max,
priority: Priority.high,
ticker: 'ticker',
icon: "app_logo",
largeIcon: DrawableResourceAndroidBitmap('app_logo'),
);
IOSNotificationDetails iOS = IOSNotificationDetails(
presentAlert: true,
);
NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics, iOS: iOS);
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
await flutterLocalNotificationsPlugin
.show(id, title, body, platformChannelSpecifics, payload: payload);
}
Map<String, dynamic> toMap() {
return {
'id': id,
'messageId': messageId,
'title': title,
'body': body,
'bigPicture': bigPicture,
'sentAt': sentAt?.millisecondsSinceEpoch,
'receivedAt': receivedAt?.millisecondsSinceEpoch,
'payload': payload,
};
}
factory PushMessage.fromMap(map) {
return PushMessage(
id: map.hashCode,
messageId: map['messageId'],
title: map['title'],
body: map['body'],
payload: map['payload'],
bigPicture: map['bigPicture'],
sentAt: map['sentAt'] is DateTime
? map['sentAt']
: (map['sentAt'] is int
? DateTime.fromMillisecondsSinceEpoch(map['sentAt'])
: null),
receivedAt: map['receivedAt'] is DateTime
? map['receivedAt']
: (map['receivedAt'] is int
? DateTime.fromMillisecondsSinceEpoch(map['receivedAt'])
: null),
);
}
factory PushMessage.fromFCM(RemoteMessage event) {
RemoteNotification? notification = event.notification;
Map<String, dynamic> data = event.data;
var noti = PushMessage(
id: event.hashCode,
messageId: event.messageId!,
title: notification?.title ?? (data['title'] ?? 'No title found'),
body: notification?.body! ?? (data['body'] ?? 'Can not find content'),
sentAt: event.sentTime,
receivedAt: DateTime.now(),
bigPicture: event.notification?.android?.imageUrl,
);
return noti;
}
Future<void> saveToHive() async {
if (!Hive.isBoxOpen('notifications')) {
await Hive.openBox<PushMessage>('notifications');
}
await Hive.box<PushMessage>('notifications').add(this);
}
String toJson() => json.encode(toMap());
factory PushMessage.fromJson(String source) =>
PushMessage.fromMap(json.decode(source));
Future<void> sendToOne(String receiverToken) async {
try {
await Dio().post(
"https://fcm.googleapis.com/fcm/send",
data: {
"to": receiverToken,
"data": {
"url": bigPicture,
"title": title,
"body": body,
"mutable_content": true,
"sound": "Tri-tone"
}
},
options: Options(
contentType: 'application/json; charset=UTF-8',
headers: {
"Authorization":
"Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
),
);
} catch (e) {
debugPrint("Error sending notification");
debugPrint(e.toString());
}
}
}
notifications.dart notification screen
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:app_name/custom_widgets/drawer_sheet.dart';
import 'package:app_name/custom_widgets/notification_expandable.dart';
import 'package:app_name/models/config.dart';
import 'package:app_name/models/push_message.dart';
class Notifications extends StatelessWidget {
const Notifications({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
if (!Hive.isBoxOpen('notifications')) {
Hive.openBox<PushMessage>('notifications');
}
return Scaffold(
appBar: AppBar(
title: const Text("Notifications"),
centerTitle: true,
),
body: ValueListenableBuilder<Box<PushMessage>>(
valueListenable: Hive.box<PushMessage>('notifications').listenable(),
builder: (context, Box<PushMessage> box, widget) {
return box.isEmpty
? const Center(child: Text('Empty'))
: ListView.builder(
itemCount: box.length,
itemBuilder: (BuildContext context, int i) {
int reversedIndex = box.length - i - 1;
return NotificationExpandable((box.getAt(reversedIndex))!);
},
);
},
),
drawer: !useDrawer
? null
: const DrawerSheet(
currentPage: "notifications",
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Hive.box<PushMessage>('notifications').clear();
},
child: const Text('Clear'),
),
);
}
}
backgroundMessageHandler
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
if (!Hive.isAdapterRegistered(1)) {
Hive.registerAdapter(PushMessageAdapter());
}
await Hive.initFlutter();
if (!Hive.isBoxOpen('notifications')) {
await Hive.openBox<PushMessage>('notifications');
}
await Firebase.initializeApp();
print('Handling a background message ${message.messageId}');
PushMessage msg = PushMessage.fromFCM(message);
await msg.saveToHive();
msg.display();
Hive.close();
}