I'm using workmanager to retrieve user's location in background every 15 minutes. When the location fetch fails, I receive a notification with the error as you can see in picture. I would like to know how can I prevent the notification to show up in failure cases.
void callbackDispatcher() {
Workmanager.executeTask((taskName, inputData) async {
if (taskName == FETCH_USER_POSITION_IN_BACKGROUND_TASK_NAME) {
// TODO: Find a better way to get user position, maybe with ServiceLocator or even better with BLoC
final dataSource = GeolocatorDataSource();
final remoteDataSource = FirestoreRemoteDataSource(
firebaseFirestore: FirebaseFirestore.instance,
);
final repository = GeolocationRepository(
geolocationDataSource: dataSource,
remoteDataSource: remoteDataSource,
);
final positionEither = await repository.getUserPosition();
positionEither.fold((failure) async {
print('failure: $failure');
}, (position) async {
print('position = $position');
final storePositionEither =
await repository.storeUserPosition(position, inputData['uid']);
storePositionEither.fold((failure) async {
print('failure: $failure');
}, (isStored) async {
print("Position has been successfully stored in background!");
});
});
}
return Future.value(true);
});
}
void _initializeWorkManagerWhenAuthenticated(String userId) {
bool isProduction = bool.fromEnvironment('dart.vm.product');
Workmanager.initialize(
callbackDispatcher,
isInDebugMode: !isProduction,
);
Workmanager.registerPeriodicTask(
FETCH_USER_POSITION_IN_BACKGROUND_TASK_ID,
FETCH_USER_POSITION_IN_BACKGROUND_TASK_NAME,
frequency: Duration(minutes: 15),
existingWorkPolicy: ExistingWorkPolicy.keep,
inputData: {
'userId': userId,
},
);
}
Have you checked if the notifications are appearing when you run with isInDebugMode: false?
See: https://github.com/fluttercommunity/flutter_workmanager/blob/ea274c33b60ef1a4e29bdd392a477f67466dc25d/lib/src/workmanager.dart#L90
Related
I am buiding an app were I want to run a batch operation in firestore and I want to run it in a different isolate. Here is my code for spawning the isolate:
Future<void> _startAnotherIsolate(String mediaUrl) async {
final isolate = await FlutterIsolate.spawn(isolate1,"hello"); // i need to pass 2 more
arguments
Timer(Duration(seconds: 5), () {
print("Pausing Isolate 1");
isolate.pause();
});
Timer(Duration(seconds: 10), () {
print("Resuming Isolate 1");
isolate.resume();
});
Timer(Duration(seconds: 20), () {
print("Killing Isolate 1");
isolate.kill();
});
}
My code for the isolate:
void isolate1(String data1, String data2) async {
await Firebase.initializeApp();
print("changing profile picture: $phone");
Timer.periodic(Duration(seconds: 1), (timer) => print("Timer Running From Isolate 1"));
var db = FirebaseFirestore.instance;
var batch = db.batch();
FirebaseFirestore.instance.collection("posts").doc(phone).collection("userPosts")
.get().then((querySnapshot) {
for (var document in querySnapshot.docs) {
try {
batch.update(document.reference,{'user_image': mediaUrl});
} on FormatException catch (error) {
// If a document ID is unparsable. Example "lRt931gu83iukSSLwyei" is unparsable.
// print("The document ${error.source} could not be parsed.");
return null;
}
}
return batch.commit();
});
}
I have seen This link and this link but they are not helpful
import 'dart:isolate';
class RequiredArgs {
late final SendPort sendPort;
late int id;
RequiredArgs(this.id, this.sendPort);
}
Future<void> main() async {
ReceivePort receivePort = ReceivePort();
RequiredArgs requiredArgs = RequiredArgs(1122, receivePort.sendPort);
Isolate isolate = await Isolate.spawn(download, requiredArgs);
var resp = await receivePort.first;
print(resp);
}
void download(RequiredArgs requiredArgs) {
final SendPort sendPort = requiredArgs.sendPort;
final id = requiredArgs.id;
print(id);
sendPort.send("yes");
}
We pass the value using the RequiredArgs class. Hope my answer helps.
I'm using awesome_notifications and flutter_background_service in conjunction to update some app state when receiving data notifications from FirebaseMessaging. As noted in the awesome_notifications, the background message handler must be a top-level function, so I am using flutter_background_service to pass data to the main isolate and update app state.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeBackgroundService();
FirebaseMessaging.onBackgroundMessage(_backgroundMessageHandler);
_initLocalNotifications();
runApp(MyApp());
}
I'm initializing the background service similarly to the example in flutter_background_service:
Future<void> initializeBackgroundService() async {
final service = FlutterBackgroundService();
await service.configure(
androidConfiguration: AndroidConfiguration(
onStart: onStart,
autoStart: true,
isForegroundMode: true,
),
iosConfiguration: IosConfiguration(
autoStart: true,
onForeground: onStart,
onBackground: onIosBackground,
),
);
await service.startService();
}
and invoking update in the _backgroundMessageHandler when a notification is received:
Future<void> _backgroundMessageHandler(
RemoteMessage message,
) async {
final service = FlutterBackgroundService();
...
service.invoke('update', {
'key1': 'val1',
'key2': 'val2',
});
}
And in the StatefulWidget for my app in the main isolate, I'm listening on the update call to receive the data:
void listenForNotificationData() {
final backgroundService = FlutterBackgroundService();
backgroundService.on('update').listen((event) async {
print('received data message in feed: $event');
}, onError: (e, s) {
print('error listening for updates: $e, $s');
}, onDone: () {
print('background listen closed');
});
}
It's never invoking the listen callback on the 'update' event. I can confirm it's calling the invoke('update') portion and calling on('update').listen, but never receiving the update. It also doesn't seem to be erroring out. Am I missing a step somewhere here?
I was encountering the same issue on flutter background service. I solved it by removing the async keyword from the callback and creating a separate async function to perform the callback operations.
void listenForNotificationData() {
final backgroundService = FlutterBackgroundService();
backgroundService.on('update').listen((event) {
print('received data message in feed: $event');
}, onError: (e, s) {
print('error listening for updates: $e, $s');
}, onDone: () {
print('background listen closed');
});
}
void action(Map? event) async {
print('received data message in feed: $event');
}
Hope it helps, forgive me if there are syntax error
You can try this.
main(){
....
}
Future<void> readyForShared() async {
var sharedPreferences = await SharedPreferences.getInstance();
counterValue = sharedPreferences.getString("yourVariable") ?? "0";
}
Future<void> saveData(String value) async {
var sharedPreferences = await SharedPreferences.getInstance();
sharedPreferences.setString("yourVariable", value);
}
#pragma('vm:entry-point')
void onStart(ServiceInstance service) async {
// Only available for flutter 3.0.0 and later
DartPluginRegistrant.ensureInitialized();
// For flutter prior to version 3.0.0
// We have to register the plugin manually
SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString("hello", "world");
/// OPTIONAL when use custom notification
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
if (service is AndroidServiceInstance) {
service.on('setAsForeground').listen((event) {
service.setAsForegroundService();
});
service.on('setAsBackground').listen((event) {
service.setAsBackgroundService();
});
}
service.on('stopService').listen((event) {
service.stopSelf();
});
// bring to foreground
Timer.periodic(const Duration(seconds: 1), (timer) async {
final receivePort = ReceivePort();
// here we are passing method name and sendPort instance from ReceivePort as listener
await Isolate.spawn(computationallyExpensiveTask, receivePort.sendPort);
if (service is AndroidServiceInstance) {
if (await service.isForegroundService()) {
//It will listen for isolate function to finish
// receivePort.listen((sum) {
// flutterLocalNotificationsPlugin.show(
// 888,
// 'Title',
// 'Description ${DateTime.now()}',
// const NotificationDetails(
// android: AndroidNotificationDetails(
// 'my_foreground',
// 'MY FOREGROUND SERVICE',
// icon: 'ic_bg_service_small',
// ongoing: true,
// ),
// ),
// );
// });
var sharedPreferences = await SharedPreferences.getInstance();
await sharedPreferences.reload(); // Its important
service.setForegroundNotificationInfo(
title: "My App Service",
content: "Updated at ${sharedPreferences.getString("yourVariable") ?? 'no data'}",
);
}
}
/// you can see this log in logcat
if (kDebugMode) {
// print('FLUTTER BACKGROUND SERVICE: ${deee.toString()}');
}
// test using external plugin
final deviceInfo = DeviceInfoPlugin();
String? device;
if (Platform.isAndroid) {
final androidInfo = await deviceInfo.androidInfo;
device = androidInfo.model;
}
if (Platform.isIOS) {
final iosInfo = await deviceInfo.iosInfo;
device = iosInfo.model;
}
service.invoke(
'update',
{
"current_date": '400',
"device": device,
},
);
});
}
....
....
....
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
readyForShared(); // init shared preferences
});
}
...
...
...
ElevatedButton(onPressed:(){saveData('Your Updated data.');}....
I am trying to get the location data on some particular time. On every time we want to fetch the location data, we are sending a silent notification to our app. When app sees a silent notification It tries to get the data and upload back to the server. Every thing is working fine when app is running in foreground, but when i close the app and clear from the background, alarm manager is not getting fired to fetch and save the location.
_firebaseMessaging.configure(
onMessage: (
Map<String, dynamic> message,
) async {
final Map data =
message.containsKey('data') ? message['data'] as Map : null;
if (data?.isEmpty ?? false) {
_handleNotification(message);
} else {
_handleSilentEvent(message); // This method will be called when server will make call to fetch location data
}
},
onResume: (Map<String, dynamic> message) async {},
onLaunch: (Map<String, dynamic> message) async {},
onBackgroundMessage: _handleSilentEvent,
);
Future<dynamic> _handleSilentEvent(
final Map<String, dynamic> message,
) async {
final Map data = message.containsKey('data') ? message['data'] as Map : null;
if (data?.isNotEmpty ?? false) {
if (data.containsKey('getLocation')) {
await LocationService.triggerServiceToGetAndSaveLocation();
} else {
_handleWebEngage(data);
}
}
}
abstract class LocationService {
static const int _alarmManagerId = 1001;
static final ILocationFacade _locationFacade = getIt<ILocationFacade>();
LocationService._();
static Future<void> triggerServiceToGetAndSaveLocation() async {
AndroidAlarmManager.oneShotAt(
DateTime.now(),
_alarmManagerId,
LocationService._fetchAndSaveLocation,
wakeup: true,
exact: true,
);
}
static Future<void> _fetchAndSaveLocation() async {
final position = await _getCurrentLocation();
final capturedAt = DateTime.now().millisecondsSinceEpoch.toString();
final gpsEnabled = await Geolocator.isLocationServiceEnabled();
final LocationInfo locationInfo = LocationInfo(
lat: position.latitude,
lng: position.longitude,
capturedAt: capturedAt,
gpsEnabled: gpsEnabled,
);
print(
'onMessage:: Lat: ${position.latitude} Long: ${position.longitude} capturedAt: $capturedAt gpsEnabled: $gpsEnabled'); // This print is getting called when app is in foreground, when i kill the app this print is not event getting called.
// await _locationFacade.saveLocationInfo(locationInfo);
}
static Future<Position> _getCurrentLocation() async {
final _gpsServiceEnabled = await Geolocator.isLocationServiceEnabled();
if (_gpsServiceEnabled) {
return Geolocator.getCurrentPosition();
}
return Geolocator.getLastKnownPosition();
}
}
Can someone point me out where i am making the mistake?
I am trying to get list of appName for all Apps installed and using package: https://pub.dev/packages/device_apps . How to run this in initstate so I can run it in background and save data in backend.
Below code prints all information while I am only looking for specific fields as list.
void initState() {
super.initState();
getinstalledAppList();
}
Future<void> getinstalledAppList() async{
List<Application> apps = await DeviceApps.getInstalledApplications();
print(apps);
}
chetan suri you can map your apps list to new one or use foreach statement. Here is example:
void initState() {
super.initState();
getinstalledAppList();
}
Future<void> getinstalledAppList() async{
List<Application> apps = await DeviceApps.getInstalledApplications();
print(apps);
// Using foreach statement
apps.forEach((app) {
print(app.appName);
// TODO Backend operation
});
}
Map apps list to new:
Class model:
class AppInfo {
String appName, packageName, versionName;
AppInfo({
this.appName,
this.packageName,
this.versionName,
});
static List<AppInfo> retrieveSomeFields(List<Application> data) {
return data
.map(
(app) => AppInfo(
appName: app.appName,
packageName: app.packageName,
versionName: app.versionName,
),
)
.toList();
}
}
Call:
Future<void> getinstalledAppList() async{
List<Application> apps = await DeviceApps.getInstalledApplications();
print(apps);
var data = AppInfo.retrieveSomeFields(apps);
// TODO Backend operation
}
You can write a work manager and callbackDispatcher for background processes. Here is a good explanation. It will look like this:
const myTask = "syncWithTheBackEnd";
void main() {
Workmanager.initialize(callbackDispatcher);
Workmanager.registerOneOffTask(
"1",
myTask, //This is the value that will be returned in the callbackDispatcher
// Set Your Delay!
initialDelay: Duration(minutes: 5),
constraints: WorkManagerConstraintConfig(
requiresCharging: true,
networkType: NetworkType.connected,
),
);
runApp(MyApp());
}
void callbackDispatcher() {
Workmanager.executeTask((task) {
switch (task) {
case myTask:
print("this method was called from native!");
// Call your own method for Android.
getinstalledAppList();
break;
case Workmanager.iOSBackgroundTask:
print("iOS background fetch delegate ran");
// Call your own method for iOS.
getinstalledAppList();
break;
}
//Return true when the task executed successfully or not
return Future.value(true);
});
}
in my main.dart i have among others those two functions:
Future<void> _fetchMasterData() async {
print("Start fetch");
var jwt = await API.attemptLogIn();
if (jwt != null) {
Map<String, dynamic> answer = jsonDecode(jwt);
if (answer['message'] == 'Auth ok') {
jwtToken = 'Bearer ' + answer['token'];
}
}
await _getArticles();
await _getMainCategories();
await _getIngredients();
await _getArticleIngredients();
print("EndMasterData fetch");
}
And
#override
void initState() {
super.initState();
_fetchMasterData();
}
What i would like to have is to wait in initState till _fethcMasterData is done bevore Widgert build is called.
Is that possible? Many thanks for any help!
Here how I use an async func in initstate;
builder() async {
favoriteDatabase =
await $FloorFavoriteDatabase.databaseBuilder('favorite_database.db')
.build();
setState(() {
favoriteDao = favoriteDatabase.favoriteDao;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
WidgetsBinding.instance.addPostFrameCallback((_) =>
getNamePreferences().then(updateName));
});
builder();
favoriteDao.findAllMoviesAsStreamW();
favoriteDao.findAllMoviesAsStream();
}
Also you can check this mini article too.
It is not possible to await in initState, so when you finish all loading process then you can call SetState method which populate your widget with actual data.
Second solution could be use of futurebuilder or streambuilder where you want to show data but it is only possible if any methods data is not dependent on each other.
Future<void> _fetchMasterData() async {
print("Start fetch");
var jwt = await API.attemptLogIn();
if (jwt != null) {
Map<String, dynamic> answer = jsonDecode(jwt);
if (answer['message'] == 'Auth ok') {
jwtToken = 'Bearer ' + answer['token'];
}
}
await _getArticles();
await _getMainCategories();
await _getIngredients();
await _getArticleIngredients();
print("EndMasterData fetch");
SetState((){}); // added line
}