How to access android Accessibility Service events from background service? - flutter

I am trying to access Accessibility from my background service app which uses flutter_background_service plugin. But I can't listen to the Accessibility events. I am also receiving an error:
Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: x-slayer/accessibility_event. Response ID: 34
But when the app is open(in recent app) the service is working fine. How do I fix it?
Here is my code. I am trying to fix this for a day, but cant find a way out:
Future<void> initializeService() async {
final service = FlutterBackgroundService();
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,
),
iosConfiguration: IosConfiguration(
// auto start service
autoStart: true,
// this will be executed when app is in foreground in separated isolate
onForeground: onStart,
// you have to enable background fetch capability on xcode project
onBackground: onIosBackground,
),
);
service.startService();
}
bool onIosBackground(ServiceInstance service) {
WidgetsFlutterBinding.ensureInitialized();
print('FLUTTER BACKGROUND FETCH');
return true;
}
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
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 {
if (service is AndroidServiceInstance) {
service.setForegroundNotificationInfo(
title: "My App Service",
content: "Updated at ${DateTime.now()}",
);
}
/// you can see this log in logcat
print('FLUTTER BACKGROUND SERVICE: ${DateTime.now()}');
void PeaceBox() {
StreamSubscription<AccessibilityEvent>? _subscription;
List<AccessibilityEvent?> events = [];
overlayRequest();
print("Started listening");
// FlutterOverlayWindow.overlayListener.listen((event) {
// print("$event");
// });
String text = "";
// print(text.split(" "));
FlutterAccessibilityService.accessStream.listen((event) {
});
}
PeaceBox();
// 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": DateTime.now().toIso8601String(),
"device": device,
},
);
});
}

Related

How can I run background code when a notification is executed with Flutter?

I am using flutter_local_notification and workmanager plugins in order to run some background code when a notification is generated (only Android). This is how flutter_local_notification is initialised:
final StreamController<ReceivedNotification> didReceiveLocalNotificationSubject = StreamController<ReceivedNotification>.broadcast();
Future<void> init() async {
await _configureLocalTimeZone();
notificationAppLaunchDetails = await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
if (notificationAppLaunchDetails!.didNotificationLaunchApp) {
selectedNotificationPayload = notificationAppLaunchDetails!.notificationResponse?.payload;
}
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('#mipmap/ic_launcher');
InitializationSettings initializationSettings = const InitializationSettings(
android: initializationSettingsAndroid,
);
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse:
(NotificationResponse notificationResponse) {
switch (notificationResponse.notificationResponseType) {
case NotificationResponseType.selectedNotification:
case NotificationResponseType.selectedNotificationAction:
// if (notificationResponse.actionId == navigationActionId) {
selectNotificationSubject.add(notificationResponse.payload);
selectedNotificationPayload = notificationResponse.payload;
// }
didReceiveLocalNotificationSubject.add(
ReceivedNotification(
id: notificationResponse.id!,
title: notificationResponse.actionId,
body: 'stuff',
payload: notificationResponse.payload,
),
);
break;
}
},
// onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
);
_notificationsEnabled = await _isAndroidPermissionGranted();
_notificationsEnabled = await _requestPermissions();
_configureDidReceiveLocalNotificationSubject();
}
and this is the code that gets executed with Workmanager:
void _configureDidReceiveLocalNotificationSubject() {
didReceiveLocalNotificationSubject.stream
.listen((ReceivedNotification receivedNotification) async {
var title = receivedNotification.title ?? 'UNKNOWN';
Workmanager().registerOneOffTask(
"my.simpleTask",
"my.simpleTask",
inputData: <String, dynamic>{
'string': title,
},
);
});
}
Currently I have two problems with that code:
the Workmanager's task is run only when the user tap the notification
the Workmanager's task won't be executed if the app is terminated by the user first, even if the notification is generated (and tapped)
How can I make the Workmanager's task to be executed as soon as the notification is generated (without the user tapping) with the application terminated or not?
i assume you are scheduled local notification and execute some function in there.
TL:DR
first: scheduled local notification not able to execute function in background
eg:
int randomInt = Random().nextInt();
await flutterLocalNotificationsPlugin.zonedSchedule(
0,
'scheduled notif with int $randomInt',
....
when you are registed this notif, you will get randomInt, and then on the notification it will show the int that you get first time its scheduled. which means, its only show notification,Random().nextInt is not executed.
secondly,
void _configureDidReceiveLocalNotificationSubject() {
didReceiveLocalNotificationSubject.stream
stream function will be terminated too after the apps is killed. except you are bring it into foreground. the stream will keep listening any changes.
How can I make the Workmanager's task to be executed as soon as the
notification is generated
I think you are missed on this part. the correct way is:
Register Workmanager and then inside the callback function, you can generated local notification.
register your WM in your initState
Workmanager().registerOneOffTask(
"task-identifier",
simpleTaskKey,
initialDelay: Duration(minutes: 30), // you can use this delay for scheduling
);
then in the callback funtion generete local notificaiton
#pragma('vm:entry-point')
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) {
// you function execute here
// eg: final tempInt = Random.nextInt();
// then we can use the tempInt
show local notification function here
return Future.value(true);
});
}

flutter_background_service not receiving updates

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.');}....

Service wont initialize when exported to seperate class

Whenever I am trying to launch background service from a seperate class then it doesn't work at all, nothing happens. However when I include all the methods under void main() in main.dart file and in main use await initializeService(); then it works. Why I can't launch this from an external class that I have created? I don't want to keep all the methods in the main file because it's getting messy. Am I doing something wrong? I am using this library
https://pub.dev/packages/flutter_background_service
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await BackgroundService().initializeService();
child: const MyApp()));
}
class BackgroundService {
Future<void> initializeService() async {
final service = FlutterBackgroundService();
await service.configure(
androidConfiguration: AndroidConfiguration(
// this will executed when app is in foreground or background in separated isolate
onStart: onStart,
// auto start service
autoStart: true,
isForegroundMode: true,
),
iosConfiguration: IosConfiguration(
// auto start service
autoStart: true,
// this will executed when app is in foreground in separated isolate
onForeground: onStart,
// you have to enable background fetch capability on xcode project
onBackground: onIosBackground,
),
);
}
// to ensure this executed
// run app from xcode, then from xcode menu, select Simulate Background Fetch
void onIosBackground() {
WidgetsFlutterBinding.ensureInitialized();
print('FLUTTER BACKGROUND FETCH');
}
void onStart() {
WidgetsFlutterBinding.ensureInitialized();
final service = FlutterBackgroundService();
service.onDataReceived.listen((event) {
if (event!["action"] == "setAsForeground") {
service.setForegroundMode(true);
return;
}
if (event["action"] == "setAsBackground") {
service.setForegroundMode(false);
}
if (event["action"] == "stopService") {
service.stopBackgroundService();
}
});
// bring to foreground
service.setForegroundMode(true);
Timer.periodic(const Duration(seconds: 3), (timer) async {
if (!(await service.isServiceRunning())) timer.cancel();
service.setNotificationInfo(
title: "My App Service",
content: "Updated at ${DateTime.now()}",
);
print("Hello");
service.sendData(
{
"current_date": DateTime.now().toIso8601String(),
// "device": device,
},
);
});
}
}

Hide background location notification in failure

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

Print and save data of list of installed apps in flutter

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);
});
}