Flutter: separating background service to different class, cause method error - flutter

Hi so I'm trying to use background_service for my app by using flutter_background_service plugin, I've managed to follow the example from the documentation and I want to move these function to different classes for better code writing. The problem is when I move it to the other file and call the method it show an error of:
Unhandled Exception: onStart method must be a top-level or static function
What causing this to happen ? can anyone explain it and how to fix it ? And how do I make my background_service app listen to my firestore data.. I want to detect if there's any task that almost pass the due_date.
Here's my code:
Main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await BackgroundService().initializeService();
}
Background_service.dart
class BackgroundService {
static const notificationChannelId = 'my_foreground';
static const notificationId = 888;
Future<void> initializeService() async {
final service = FlutterBackgroundService();
const AndroidNotificationChannel channel = AndroidNotificationChannel(
notificationChannelId, // ID
'MY FOREGROUND SERVICE', // Title
description:
'This channel is used for important notifications.', // Description
importance: Importance.low, // importance must be at low or higher level
);
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
if (Platform.isIOS) {
await flutterLocalNotificationsPlugin.initialize(
const InitializationSettings(
iOS: IOSInitializationSettings(),
),
);
}
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
await service.configure(
androidConfiguration: AndroidConfiguration(
onStart: onStart,
autoStart: true,
isForegroundMode: true,
),
iosConfiguration: IosConfiguration(
autoStart: true,
onForeground: onStart,
onBackground: _onIosBackground,
),
);
service.startService();
}
onStart(ServiceInstance service) async {
DartPluginRegistrant.ensureInitialized();
/// Code about _onstrat method
}
#pragma('vm:entry-point')
Future<bool> _onIosBackground(ServiceInstance service) async {
/// Code about Ios background
}
}

top-level means the function should be sits on the top of any
hierarchy of Class or Function.
you have to put the fucntion outside of classes. read documentation here: https://dart.dev/guides/language/effective-dart/design#avoid-defining-a-class-that-contains-only-static-members
Workaround:
background_service.dart
// on start is a top-level function
#pragma('vm:entry-point')
onStart(ServiceInstance service) async {
DartPluginRegistrant.ensureInitialized();
/// Code about _onstrat method
}
// top-level function
#pragma('vm:entry-point')
Future<bool> _onIosBackground(ServiceInstance service) async {
/// Code about Ios background
}
class BackgroundService {
static const notificationChannelId = 'my_foreground';
static const notificationId = 888;
......
await service.configure(
androidConfiguration: AndroidConfiguration(
onStart: onStart,
),
iosConfiguration: IosConfiguration(
onForeground: onStart,
onBackground: _onIosBackground,
),
.......

Related

Firebase Push Notification is Not working Properly

i have implemented firebase push notification in my project with payload json,
I have done all the configurations and now when i click on the notification, it works sometimes perfectly but sometimes the redirection just doesn't work, let's say if i click the notification 10 times, it redirects to the desired page for 4 times and rest 6 times, its taking me to home screen. I am sharing my code below.
Main File
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
DartPluginRegistrant.ensureInitialized();
await Firebase.initializeApp();
await SharedPref.initialize();
initilizeCamera();
initializeDb();
FlutterError.onError = (error) {
FirebaseCrashlytics.instance.recordFlutterFatalError(error);
};
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((_) {
runApp(
const ProviderScope(child: MyApp()),
);
});
await pushNotificationToken();
/// Get initial message
await FirebaseMessaging.instance.getInitialMessage();
notificationActions(
action: (RemoteMessage? message) {
if (kDebugMode) {
if (message?.notification != null) {
}
}
if (message != null) {
notificationAction(message: message);
}
},
localNotification: true,
localNotificationAction: (payload) {
if (kDebugMode) {
}
notificationAction(payload: payload);
});
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
systemNavigationBarColor: Colors.white,
statusBarColor: Colors.white,
));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return KeyboardDismissOnTap(
child: GetMaterialApp(
theme: appThemeData,
debugShowCheckedModeBanner: false,
title: 'XYZ',
// Start the app with the "/" named route. In this case, the app starts
// on the FirstScreen widget.
initialRoute: '/',
initialBinding:XYZBindings(),
routes: {
// When navigating to the "/" route, build the FirstScreen widget.
'/': (context) => const XYZScreen(),
}),
);
}
}
Firebase Notification Code
/// Firebase messaging instance
FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
/// Get firebase notification instance
FirebaseMessaging get firebaseMessagingInstance => _firebaseMessaging;
/// Firebase push notification token
Future<String?> pushNotificationToken() async =>
_firebaseMessaging.getToken().then((value) {
Log.w('FCM TOKEN :: $value');
return value;
});
/// When user clicks on the notification
void notificationActions({
#required Function(RemoteMessage?)? action,
bool localNotification = false,
Function(Map<String, dynamic>)? localNotificationAction,
}) {
/// Make sure localNotification is Not Null
if (localNotification) {
assert(localNotificationAction != null);
}
if (Platform.isIOS) {
_requestPermissions();
}
if (localNotification) {
FlutterLocalNotificationHelper().initializeSettings(
actionCallback: localNotificationAction,
);
}
/// Get initial message
_firebaseMessaging.getInitialMessage().then((RemoteMessage? message) {
action!(message);
});
/// On Message
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
if (localNotification) {
await FlutterLocalNotificationHelper().showNotificationWithDefaultSound(
title: message.notification?.title,
body: message.notification?.body,
payload: jsonEncode(message.data),
);
}
});
/// On Message open app
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
action!(message);
});
}
/// Request permission for iOS
void _requestPermissions() {
_firebaseMessaging.requestPermission(
provisional: true,
);
}
Local Notification Page
/// Helper class for local notifications
class FlutterLocalNotificationHelper {
/// Constructor
factory FlutterLocalNotificationHelper() => _instance;
FlutterLocalNotificationHelper._();
/// Flutter LocalNotification Helper instance
static final FlutterLocalNotificationHelper _instance =
FlutterLocalNotificationHelper._();
/// Flutter LocalNotification for iOS Category
static const String darwinNotificationCategory = 'plainCategory';
/// Notification plugin
late FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin;
/// Flutter LocalNotifications Plugin
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
/// Request local notification
Future<void> requestLocalNotification() async {
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.requestPermission();
}
/// local notification plugin callback
late Function(Map<String, dynamic>) _localNotificationCallback;
/// Initialize local notifications
Future<void> initializeSettings({
#required Function(Map<String, dynamic>)? actionCallback,
}) async {
if (actionCallback != null) {
_localNotificationCallback = actionCallback;
}
/// initialize settings for => Android
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('#mipmap/ic_launcher');
/// initialize settings for => iOS
const DarwinInitializationSettings initializationSettingsIOS =
DarwinInitializationSettings(
requestSoundPermission: false,
requestBadgePermission: false,
requestAlertPermission: false,
);
/// Initialize settings for both => Android / iOS
const InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
/// Flutter local notification plugin
_flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
/// Local notification plugin initialize
await _flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse: (NotificationResponse response) {
if (response.payload != null) {
_localNotificationCallback(json.decode(response.payload!));
}
},
);
/// On notification click
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
await _flutterLocalNotificationsPlugin
.getNotificationAppLaunchDetails();
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
await onSelectNotification(
notificationAppLaunchDetails?.notificationResponse.toString(),
);
}
}
/// WHEN USER CLICKS TO NOTIFICATION
Future<void> onSelectNotification(String? payload) async {
if (payload != null) {
final Map<String, dynamic> payloadJson =
jsonDecode(payload) as Map<String, dynamic>;
_localNotificationCallback(payloadJson);
}
}
/// SHOW LOCAL NOTIFICATION
/// 'your channel id', 'your channel name', IS NOT NEEDED
Future<void> showNotificationWithDefaultSound({
#required String? title,
#required String? body,
#required String? payload,
}) async {
/// Android notification details
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'your channel id',
'your channel name',
importance: Importance.max,
priority: Priority.high,
color: AppColors.red,
icon: '#mipmap/ic_launcher',
);
/// iOS Notification details
const DarwinNotificationDetails iOSPlatformChannelSpecifics =
DarwinNotificationDetails(
categoryIdentifier: darwinNotificationCategory,
presentSound: true,
presentAlert: true,
badgeNumber: 0,
);
/// Notification platforms details
const NotificationDetails platformChannelSpecifics = NotificationDetails(
android: androidPlatformChannelSpecifics,
iOS: iOSPlatformChannelSpecifics,
);
/// Show notification
await _flutterLocalNotificationsPlugin.show(
0,
title,
body,
platformChannelSpecifics,
payload: payload,
);
}
}
Notifications Action page
void notificationAction(
{RemoteMessage? message, Map<String, dynamic>? payload}) {
The redirection is written here.
}
please let me know that why sometimes the redirection works and sometimes not.

Futter background notification with Navigation

i listen to the notification as below
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
await FirebaseMessaging.instance.getToken();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(MyProject(
appRouter: AppRouting(),
));
}
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// If you're going to use other Firebase services in the background, such as Firestore,
// make sure you call `initializeApp` before using other Firebase services.
await Firebase.initializeApp();
NotificationServices ntService = NotificationServices();
print("Handling a background message: ${message.data['room']}");
ntService.showNotification(message);
}
void showNotification(RemoteMessage message) {
flutterLocalNotificationsPlugin.show(
message.data.hashCode,
message.data['title'],
message.data['body'],
NotificationDetails(
android: AndroidNotificationDetails(
NotificationServices.channel.id,
NotificationServices.channel.name,
channelDescription: NotificationServices.channel.description,
//channel.description,
icon: 'launch_background',
playSound: true,
// other properties...
),
),
payload: message.data['room']);
}
}
await flutterLocalNotificationsPlugin.initialize(
initialzationSettings,
onDidReceiveNotificationResponse: onSelectNotification,
onDidReceiveBackgroundNotificationResponse: onSelectNotification,
);
in the onSelectNotification
i get the roomID and then navigate to it but to do so i had to but it in the homepage screen in order to set the user and room data so i get an error i should use in the top level function in the main.dart and it is working fine with the foreground notification but for the background it just open the app so any ideas of how should i do it

(FLUTTER Android) flutter alarm manager not open app at the time when app is terminated

I'm make a flutter alarm app (with alarm_manager_plus).
When the app call
AndroidAlarmManager.oneShot(Duration(seconds:10),...);
On foreground, it wroks well, but when app is terminated (or mobile screen off) the app couldn't launch.
How could I deal with it?
https://pub.dev/packages/android_alarm_manager_plus/example
I tried this example.
in order to use app services in background you need to use this plugin
https://pub.dev/packages/flutter_background_service
example code
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeService();
runApp(MyApp());
}
// this will be used as notification channel id
const notificationChannelId = 'my_foreground';
// this will be used for notification id, So you can update your custom notification with this id.
const notificationId = 888;
Future<void> initializeService() async {
final service = FlutterBackgroundService();
const AndroidNotificationChannel channel = AndroidNotificationChannel(
notificationChannelId, // id
'MY FOREGROUND SERVICE', // title
description:
'This channel is used for important notifications.', // description
importance: Importance.low, // importance must be at low or higher level
);
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
await service.configure(
androidConfiguration: AndroidConfiguration(
// this will be executed when app is in foreground or background in separated isolate
onStart: onStart,
// auto start service
autoStart: true,
isForegroundMode: true,
notificationChannelId: notificationChannelId, // this must match with notification channel you created above.
initialNotificationTitle: 'AWESOME SERVICE',
initialNotificationContent: 'Initializing',
foregroundServiceNotificationId: notificationId,
),
...

Flutter Firebase Cloud Messaging how to Auto Dismiss/Cancel a notification?

I have successfully managed to receive FCM messages(on my mobile) via the console as well as from my NodeJs server. But how may I send & receive an FCM message that will arrive at my phone, do some tasks and then auto Cancel/Dismiss by itself?
Is this possible in Flutter with FCM?
In Android we use to have public Notification.Builder setTimeoutAfter (long durationMs)
Its more for just pinging the client app... and retrieving some data from the Apps local storage. Since it can be done automatically i want to do it without troubling the user.
These are the steps to accomplish the following
receiving a notification without disturbing the user(silently without any Alert in the system tray)
let localNotification Pkg start the progress Notification
do a background task and when finished
cancel the notification via LocalNotifications Pkg
Make sure you have the following in your .yaml file... at the time of solving this I had the following versions:
firebase_messaging: ^11.1.0
firebase_core: ^1.10.0
flutter_local_notifications: ^9.1.
For the Local Notification Package lets make a class to use its services
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class LocalNotificationService {
static final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin();
static void initialize(BuildContext context) {
final InitializationSettings initializationSettings = InitializationSettings(
android: const AndroidInitializationSettings("#mipmap/your_icon"));
_notificationsPlugin.initialize(initializationSettings);
}
//=================================================
//==============this is the update notification
static Future<void> showProgressNotification() async {
const int maxProgress = 5;
for (int i = 0; i <= maxProgress; i++) {
await Future<void>.delayed(const Duration(seconds: 1), () async {
final AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails('progress channel', 'progress channel',
channelDescription: 'progress channel description',
channelShowBadge: false,
importance: Importance.max,
priority: Priority.high,
playSound: false,
showProgress: true,
maxProgress: maxProgress,
progress: i);
final NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
await _notificationsPlugin.show(
0,//I use this id to cancel it from below method
'progress notification title',
'progress notification body',
platformChannelSpecifics,
payload: 'item x');
});
}
}
//=========================and this is for the ProgressNotification to be cancelled
static Future<void> cancelNotification() async {
await _notificationsPlugin.cancel(0);
}
}//end of class
Make your you initialize it in the init method of your Widget
#override
void initState() {
// TODO: implement initState
super.initState();
LocalNotificationService.initialize(context);
}
And lastly... this is how your Main() and top handler will look
//Receive message when app is in background/minimized
//THIS IS THE TOP LEVEL HANDLER.. as it is outside the scope of main()
Future<void> backgroundHandler(RemoteMessage message) async{
print("from the Background Handler Top Function()..............");
print(message.data.toString());
//now for the localNotification to take over
await LocalNotificationService.showProgressNotification();
await Future<void>.delayed(const Duration(seconds: 2));//faking task delay
await LocalNotificationService.cancelNotification();//by default I have made id=0
}
void main() async{
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(backgroundHandler);//this has to be a TOP LEVEL METHOD
runApp(MyApp());
}
And on the Server side while sending the notification make sure there is only data{}... see #Junsu Cho answer
ios is impossible
android is possible, remove notification payload
{
"to": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1..."
"notification":
{
"title": "title"
}
"data":
{
"data" : "data"
}
}
to
{
"to": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1..."
"data":
{
"data" : "data"
}
}

Migrating Push notification from firebase_messaging 6 to firebase_messaging 10+

I am coming from an old Firebase_messaging plugin 6 + to the newer Firebase_messenger plugin 10 +, I am able to do most of the thing but can't get the message data, I want to convert this code from the older plugin to a newer one and use methods like configure launch and onResume.I can receive the push notifications, foreground and background information about the message but can't read it.
class _ChatScreenState extends State<ChatScreen> {
#override
void initState() {
super.initState();
final fbm = FirebaseMessaging();
fbm.requestNotificationPermissions();
fbm.configure(onMessage: (msg) {
print(msg);
return;
}, onLaunch: (msg) {
print(msg);
return;
}, onResume: (msg) {
print(msg);
return;
});
}
What I have done so far
Under AndroidManifest.xml added
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="high_importance_channel" />
On main.dart
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print('Handling a background message ${message.messageId}');
}
Future <void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(MyApp());
}
Here is where I want to get the data from the server
class _ChatScreenState extends State<ChatScreen> {
#override
void initState() {
FirebaseMessaging.instance
.getInitialMessage()
.then((RemoteMessage message) {
if (message != null) {
print(message);
}
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Got a message whilst in the foreground!');
print('Message data: ${message.data}');
if (message.notification != null) {
print(
'Message also contained a notification: ${message.notification}');
}
});
});
super.initState();
}
On Debug while the App is in the foreground
D/FLTFireMsgReceiver(15437): broadcast received for message
I/flutter (15437): Got a message whilst in the foreground!
I/flutter (15437): Message data: {}
I/flutter (15437): Message also contained a notification: Instance of 'RemoteNotification'
On Background
D/FLTFireMsgReceiver(15437): broadcast received for message
W/FirebaseMessaging(15437): Missing Default Notification Channel metadata in AndroidManifest. Default value will be used.
I/flutter (15437): Handling a background message 0:1624718321445677%ba7e1d8bba7e1d8b
Although the Notification Text and body is fine in the notification window but can't get the same info in the debug screen, it returns empty. Also is my implementation correct?
With a little bit of searching I believe the missing piece was flutter_local_notifications: ^5.0.0+4 The changes I have made in
main.dart
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// If you're going to use other Firebase services in the background, such as Firestore,
// make sure you call `initializeApp` before using other Firebase services.
await Firebase.initializeApp();
print('Handling a background message ${message.messageId}');
}
const AndroidNotificationChannel channel = const AndroidNotificationChannel(
//for notificaiton initialization
'high_importance_channel', // id
'High Importance Notifications', // title
'This channel is used for important notifications.', // description
importance: Importance.high,
playSound: true,
);
//initialize plugin
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
//for background messaging
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
//Local Notification implementation
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
//for firebase plugin and messaging required
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true, badge: true, sound: true);
runApp(MyApp());
}
In the class where I want the notification
class _ChatScreenState extends State<ChatScreen> {
#override
void initState() {
//foreground messaging
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
RemoteNotification notification = message
.notification; //assign two variables for remotenotification and android notification
AndroidNotification android = message.notification?.android;
if (notification != null && android != null) {
print(message);
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id, channel.name, channel.description,
color: Colors.blue,
playSound: true,
icon: '#mipmap/ic_launcher'),
),
);
print(notification.title);
print(notification.body);
}
});
//Do when the user taps the notification
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
//same as above
});
super.initState();
}
Would be nice if some one update this process for IOS as I don't have a developer Id to test it.