Flutter & Background fetch - How to handle a simple example? - flutter

I am really wondering how to use the package https://pub.dev/packages/background_fetch.
I want to run a simple task to grab entries from my sqllite DB to change status.
On the main.dart file, I have :
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
initPlatformState();
await DotEnv().load('.env');
runApp(MyApp());
// Register to receive BackgroundFetch events after app is terminated.
// Requires {stopOnTerminate: false, enableHeadless: true}
BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
BackgroundFetch.start();
}
First, I am really not if I have to put : BackgroundFetch.start(); ?
Then, here is my function called :
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
// Configure BackgroundFetch.
BackgroundFetch.configure(BackgroundFetchConfig(
minimumFetchInterval: 15,
stopOnTerminate: false,
enableHeadless: false,
requiresBatteryNotLow: false,
requiresCharging: false,
requiresStorageNotLow: false,
requiresDeviceIdle: false,
requiredNetworkType: NetworkType.ANY
), (String taskId) async {
// This is the fetch-event callback.
print("[BackgroundFetch] Event received $taskId");
// take all photos not uploaded
final repo = di.get<DossierRepository>();
final photosNotUploaded = await repo.getPhotosNotUploaded();
await for (final photo in Stream.fromIterable(photosNotUploaded)) {
// resend them simply
repo.resendPhoto(photo.dossierId, photo.photoId);
}
// IMPORTANT: You must signal completion of your task or the OS can punish your app
// for taking too long in the background.
BackgroundFetch.finish(taskId);
}).then((int status) {
print('[BackgroundFetch] configure success: $status');
}).catchError((e) {
print('[BackgroundFetch] configure ERROR: $e');
});
}
I have this warning :
[TSBackgroundFetch start] Task flutter_background_fetch already registered
I understand I have to check if the task is not registered, but how ? And if I make changes to my code, I need to "destroy" the old task and register the new one ?
And is it the good way of using this plugin ? I have only 1 Task for my app, I want it to run all the time. Do I have to register Tasks ? Or this is enough ?

flutter_background_fetch is the default taskid of BackgroundFetch, which will be auto-registered by BackgroundFetch when initPlatformState. It means that you should not use flutter_background_fetch as a taskid.

Related

Access Variable form another class in top level function Flutter

I have a background notification handler in my flutter app, which is a top-level function like so:
Future<void> _onBackgroundMessage(RemoteMessage message) async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final chatClient = StreamChatClient(STREAM_API_KEY);
print(Utils.user?.uid);
print(Utils.user?.streamToken);
chatClient.connectUser(
su.User(id: Utils.user?.uid ?? ''),
Utils.user?.streamToken ?? '',
connectWebSocket: false,
);
NotificationUtils.handleGetStreamNotification(message, chatClient);
SharedPreferences prefs = await SharedPreferences.getInstance();
int appBadgeCounter = prefs.getInt(appBadgePrefsKey) ?? 0;
FlutterAppBadger.updateBadgeCount(appBadgeCounter + 1);
}
In the notification handler, I need to connect to a separate server using a uid and a token. I have a Utils singleton class where I store an app user as a variable. I initialize this Utils app variable in my home page stateful widget class and persist it throughout the app. This way, I can minimize my number of database calls for user data.
However, in this top-level notification handler, the Utils.user is always null. This top level function exists in my home page stateful widget class, but it is a top level function still.
I want to avoid making database calls for every single notification that the app receives. Is there a way to get this Utils. user data without getting null??
you have to setUser before using it in chatClient.connectUser and along with you can check if user is null or not if null initialize it then it stops does not make extra calls.
Future<void> _onBackgroundMessage(RemoteMessage message) async {
...
await Firebase.initializeApp();
final chatClient = StreamChatClient(STREAM_API_KEY);
if(Utils.user != null) {
Utils.setUser();
}
print(Utils.user?.uid);
print(Utils.user?.streamToken);
...
}

How to Flutter App run continuously fetch data after every 10 sec in background when app is closed/terminated or screen off

How to Flutter App run continuously in the background when the app is closed/terminated. I have to fetch the location continuously when the app is terminated/closed or the screen is locked.
Try work manager
https://pub.dev/packages/workmanager
Create a root level method and use that method to create a background process.
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) {
print("Native called background task: $backgroundTask"); //simpleTask will be emitted here.
return Future.value(true);
});
}
void main() {
Workmanager().initialize(
callbackDispatcher, // The top level function, aka callbackDispatcher
isInDebugMode: true // If enabled it will post a notification whenever the task is running. Handy for debugging tasks
);
Workmanager().registerOneOffTask("task-identifier", "simpleTask");
runApp(MyApp());
}

Flutter Local Notifications doesn't execute code with app terminated

I have an app that schedules alarms, which trigger flutter local notifications. When pressing one of the buttons on these notifications, the database gets updated to reflect that you've turned off the alarm.
However, when the app is terminated/closed down, it seems as if no code gets executed and the database doesn't get updated.
The notification does get triggered, it's when the user presses a button inside of it that no code gets executed. Looks something like this
I'd like to know if what I'm trying is even possible, because at this point I'm not sure.
The notifications are created using the zonedSchedule from the flutter local notifications plugin.
The app does not get opened when pressing one of the buttons in the notification.
The code is as follows:
class LocalNoticationsService {
LocalNoticationsService(
this._notificationsPlugin,
);
final FlutterLocalNotificationsPlugin _notificationsPlugin;
Future<void> init() async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings(
'img/path',// path to notification icon
);
final DarwinInitializationSettings initializationSettingsIOS = DarwinInitializationSettings(
requestSoundPermission: false,
requestBadgePermission: false,
requestAlertPermission: false,
defaultPresentAlert: true,
defaultPresentSound: true,
notificationCategories: [
DarwinNotificationCategory(
'category',
options: {
DarwinNotificationCategoryOption.allowAnnouncement,
},
actions: [
DarwinNotificationAction.plain(
'snoozeAction',
'snooze',
),
DarwinNotificationAction.plain(
'confirmAction',
'confirm',
options: {
DarwinNotificationActionOption.authenticationRequired,
},
),
],
),
],
);
final InitializationSettings initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
await _notificationsPlugin.initialize(
initializationSettings,
onSelectNotification: _onSelectedNotification,
onSelectNotificationAction: onSelectNotificationAction,
);
final notificationOnLaunchDetails = await _notificationsPlugin.getNotificationAppLaunchDetails();
if (notificationOnLaunchDetails?.didNotificationLaunchApp ?? false) {
_onSelectedNotification(notificationOnLaunchDetails!.payload);
}
}
void _onSelectedNotification(String? payload) {
// Not relevant to this problem
}
Future<bool> _confirmAlarm(AlarmPayLoad alarmPayload) async {
// Update database
}
}
// Top level function
Future<void> onSelectNotificationAction(NotificationActionDetails details) async {
await Firebase.initializeApp();
final localNotificationsService = LocalNoticationsService(
FlutterLocalNotificationsPlugin()
);
await localNotificationsService.init();
final alarmPayload = AlarmPayLoad.fromJson(details.payload);
await localNotificationsService._confirmAlarm(alarmPayload)
}
Due to this all running in the background, I've been unable to find a way to log things. I've tried using the http package to send post request to a webhook with some data, but these do not seem to trigger when the app is terminated, but do work when in the app is in the foreground or in the background(not closed).
Any post or videos I've found online about flutter local notifications don't seem to cover this problem or are using firebase push notifications and the associated package for background handling.
I'd be very interested to hear any feedback on what might be going wrong or what might be the solution and how handling background notifications works. If you need additional information on the code, feel free to ask.
If you want to run it on background, follow the link here.

Getting null argument while routing through firebase push notifications flutter

I am trying to route to a certain screen with some argument when clicked on push notifications. So far it is working fine when app is in foreground or in background but open. But when the app is terminated it is routing to the correct screen but there is some issue with the argument, it is null.
const payload = admin.messaging.MessagingPayload = {
data : {
'type' : 'msg',
'route' : 'chat-screen',
'argument' : sentby,
},
notification : {
title : senderData.user_name,
body: original.message,
image: notificationIcon,
android_channel_id : "Finiso",
channel_id : "Finiso",
clickAction : 'FLUTTER_NOTIFICATION_CLICK',
}
}
This is the code for the notification which I am triggering through cloud functions.
Future<void> backgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print(message.data.toString());
print(message.notification.title);
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(backgroundHandler);
added this in main.dart
FirebaseMessaging.instance.getInitialMessage().then((message) async {
if (message != null) {
print(message.data);
routeNotification(message);
}
});
listening to messages and on app open trigger.
void routeNotification(RemoteMessage message) {
final routeName = message.data['route'];
if (routeName == ActivityFeed.routeName) {
Navigator.of(context).pushNamed(ActivityFeed.routeName);
} else {
if (message.data['type'] == 'msg') {
Navigator.pushNamed(
context,
message.data['route'],
arguments: message.data['argument'],
);
}
}
}
This is the routing function I used above
Image showing console logs
It is printing the first two lines from main.dart and "null" is the argument I am trying to get.
Can anyone help me on this. I have no idea what's going on.
Thank you in advance.
I have an answer but I'm not positive on the reason why this is happening yet. Let's collaborate on this.
First of all, I think the routing is happening automatically for you. In all other scenarios except the terminal state, you are pushing the route and handling adding the arguments. In the terminal state, the app is ignoring your initialRoute and using the route from your push notification.
Usually when you setup MaterialApp your initialRoute is used:
MaterialRoute(
initialRoute: '/whatever_you_put_here'
...
)
It seems that WidgetsBinding.instance.platformDispatcher.defaultRouteName (source) is being set within the flutter code and this is causing your default route to be this route instead of whatever you are passing to MaterialApp.
Reading the flutter documentation, it looks like this property is only set when someone calls FlutterView.setInitialRoute from Android.
That is the answer to your question.
--
I don't know what is calling FlutterView.setInitialRoute. A search of FlutterFire code seems to show no instances.

Flutter send local notification by API in background

I have an API that I'm checking, and when the response changes I need to send a notification to the user. I would like to know how to do this without FCM Push Notifications.
I'm using flutter-local-notifications and background fetch (https://github.com/transistorsoft/flutter_background_fetch) to do it. On the background fetch docs it says that background fetch will do your function once every 15 minutes, which is good enough for me.
This is my initPlatformState():
Future<void> initPlatformState() async {
// Load persisted fetch events from SharedPreferences
SharedPreferences prefs = await SharedPreferences.getInstance();
String json = prefs.getString(EVENTS_KEY);
if (json != null) {
setState(() {
_events = jsonDecode(json).cast<String>();
});
}
// Configure BackgroundFetch.
BackgroundFetch.configure(
BackgroundFetchConfig(
minimumFetchInterval: 15,
stopOnTerminate: false,
enableHeadless: true,
forceReload: true,
startOnBoot: true,
),
_onBackgroundFetch)
.then((int status) {
print('[BackgroundFetch] SUCCESS: $status');
setState(() {
_status = status;
});
}).catchError((e) {
print('[BackgroundFetch] ERROR: $e');
setState(() {
_status = e;
});
});
// Optionally query the current BackgroundFetch status.
int status = await BackgroundFetch.status;
setState(() {
_status = status;
});
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
}
I'm assuming that what's in the function that gets called in the fetch isn't needed for the question.
I tried this on my phone and simulator, and I used Xcode -> Simulate Background Fetch and it ran properly. It also ran properly when I opened the app. Unfortunately, it didn't run after 15 minutes. Is there something I'm missing?
How would I change my code to make the background fetch to happen every 15 minutes?
Yes, I spent lot of time trying to solve this but was not successful so I switched, I guessed what you are trying to do is to handle your own notification without Firebase or from an API like me, well this is it, after my search I was able to do this with the help of work manager package. So easy to use and implement, try it.
https://pub.dev/packages/workmanager