Dev-hwang / flutter_foreground_task

This plugin is used to implement a foreground service on the Android platform.
https://pub.dev/packages/flutter_foreground_task
MIT License
149 stars 109 forks source link

I don't think it's working properly in the background. #274

Closed cho-4 closed 1 month ago

cho-4 commented 2 months ago

I'm the one who said last time that onRepeatEvent() wasn't working properly.

I am actually testing on a device using Android 14 OS. When I connect the device to a PC and check the log, it works properly in debug mode, but when I disconnect from the PC and check the function, it does not work properly whether in debug mode or release mode.

in main.dart `void startCallback() { FlutterForegroundTask.setTaskHandler(MyTaskHandler()); }

void main() { widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); FlutterForegroundTask.initCommunicationPort(); runApp(const MyApp()); } class MyTaskHandler extends TaskHandler {

@override void onStart(DateTime timestamp) { } @override void onRepeatEvent(DateTime timestamp) { FlutterForegroundTask.sendDataToMain(359); } @override void onDestroy(DateTime timestamp) { print('foreground service end'); } void onNotificationPressed() { FlutterForegroundTask.launchApp('/'); print('onNotificationPressed'); } }

void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback((_) { _requestPermissions(); _initService(); }); }

Future _initService() async { FlutterForegroundTask.init( androidNotificationOptions: AndroidNotificationOptions( channelId: 'foreground_service', channelName: 'Foreground Service Notification', channelDescription: 'This notification appears when the foreground service is running.', channelImportance: NotificationChannelImportance.LOW, priority: NotificationPriority.LOW, ), iosNotificationOptions: const IOSNotificationOptions( showNotification: false, playSound: false, ), foregroundTaskOptions: ForegroundTaskOptions( eventAction: ForegroundTaskEventAction.repeat(360000), autoRunOnBoot: true, autoRunOnMyPackageReplaced: true, allowWakeLock: true, allowWifiLock: false, ), ); } `

in mainscreen.dart `void _onReceiveTaskData(dynamic data) { sleepDataBloc.saveSleepBackupData(data); }

void initState() { super.initState(); FlutterForegroundTask.addTaskDataCallback(_onReceiveTaskData); ` According to the code, the taskhandler sends data 359 to mainUI once every 6 minutes and stores the value. This works fine when connected to a PC, but it cannot be run on the device alone. The version is 8.8.0, the most recent version, and the battery limit setting is also unlocked.

Dev-hwang commented 2 months ago

Is there any reason to store data by sending it to the main isolate?

TaskHandler runs in background isolate. This can continue to survive in the background with the foreground service.

but, Flutter app runs in main isolate. It may be paused or detached in the background, so internal logic or services may not work properly.

What do you think about saving the value to the database in the onRepeatEvent callback, and then updating the screen by getting the value from the database when the Flutter app resumes?

Dev-hwang commented 2 months ago

Debugging mode does not pause the app. It just looks like it's working normally, but it's not.

cho-4 commented 2 months ago

To explain my current situation in more detail, mainscreen.dart is saving data to sleepdatabloc, a singleton object, approximately once every 10 seconds.

However, saving this directly to the database requires processing more resources than expected, so I tried to use the logic to save it to the database in onRepeatEvent().

However, taskhandler cannot access the singleton object I saved in mainscreen.dart, so the value cannot be saved in the database.

Dev-hwang commented 2 months ago

Yes. As I said last time, memory is not shared between the two isolates.

In conclusion, if you want to save data in the background, you should not save data in the Widget(mainscreen.dart).

Widgets can be destroyed at any time.

Can you provide sample project? What features do you want? I can help you.

cho-4 commented 2 months ago

@Dev-hwang Sorry for the late reply. Thank you for your kindness.

However, I don't think I can share the sample project due to personal reasons.

Instead, may I ask you a little more about this situation?

You said not to save data in the widget, so how should I save the data, for example?

I need to save the data handled by mainscreen.dart. So, is there a way to know the data in mainscreen.dart (widget)?

Dev-hwang commented 2 months ago

@cho-4

Please check just one thing.

Run this task and check whether notificationText is updated.

class MyTaskHandler extends TaskHandler {
  int _count = 0;

  @override
  void onStart(DateTime timestamp) {
    _updateNotificationText();
  }

  @override
  void onRepeatEvent(DateTime timestamp) {
    _count++;
    _updateNotificationText();
  }

  @override
  void onDestroy(DateTime timestamp) {
    //
  }

  void _updateNotificationText() {
    FlutterForegroundTask.updateService(notificationText: 'count: $_count');
  }
}

Please tell me if notificationText is not updated.

Dev-hwang commented 2 months ago

@cho-4

The following functions manage synchronized data between TaskHandler and main isolate.

void function() async {
  await FlutterForegroundTask.getData(key: String);
  await FlutterForegroundTask.getAllData();
  await FlutterForegroundTask.saveData(key: String, value: Object);
  await FlutterForegroundTask.removeData(key: String);
  await FlutterForegroundTask.clearAllData();
}

You can get data when the flutter app is resumed

class MyTaskHandler extends TaskHandler {
  int _count = 0;

  @override
  void onStart(DateTime timestamp) {
    //
  }

  @override
  void onRepeatEvent(DateTime timestamp) async {
    _count++;

    FlutterForegroundTask.saveData(key: 'count', value: _count);

    // for foreground state
    if (await FlutterForegroundTask.isAppOnForeground) {
      FlutterForegroundTask.sendDataToMain(_count);
    }
  }

  @override
  void onDestroy(DateTime timestamp) {
    //
  }
}

class MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
  void _onReceiveTaskData(Object data) {
    sleepDataBloc.saveSleepBackupData(data);
  }

  void _onAppResumed() async {
    final int? data = await FlutterForegroundTask.getData<int>(key: 'count');
    if (date != null) {
      sleepDataBloc.saveSleepBackupData(data);
    }
  }

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);

    // Add a callback to receive data sent from the TaskHandler.
    FlutterForegroundTask.addTaskDataCallback(_onReceiveTaskData);
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    // for background state
    if (state == AppLifecycleState.resumed) {
      _onAppResumed();
    }
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);

    // Remove a callback to receive data sent from the TaskHandler.
    FlutterForegroundTask.removeTaskDataCallback(_onReceiveTaskData);
    super.dispose();
  }
}
cho-4 commented 1 month ago

@Dev-hwang I ran the code you showed, but the norificationText is still not updated in release mode. But it works fine in debug mode.

Dev-hwang commented 1 month ago

I tested it with the example app for about a day and it worked normally.

tested env
Flutter 3.24.2 / Dart 3.5.2
Flutter 3.10.0 / Dart 3.0.0

tested device
- Galaxy Note 10 (Android 12)
- Galaxy Fold 4 (Android 14)
- Galaxy Fold 5 (Android 14)

https://drive.google.com/file/d/1G7RRp5X6MLIXQww3HRCp0nRlEBtcgTsH/view?usp=sharing

The service does not seem to be running in the background due to a specific manufacturer's problem or an unknown problem. Similar issues are being reported in other plugins.

Dev-hwang commented 1 month ago

If the app I provided runs normally, this may not be the above problem.

It will be difficult to solve the issue without detailed simulation and sample project to reproduce the problem.

cho-4 commented 1 month ago

@Dev-hwang I also use the latest versions of both Flutter and Dart, and my actual device is Galaxy S21 and uses Android 14.

In case it's a device problem, the other device, Galaxy A235 Android 14, still doesn't work in release mode.

Dev-hwang commented 1 month ago

@cho-4

Did you add pragma annotation?

@pragma('vm:entry-point')
void startCallback() {
  // The setTaskHandler function must be called to handle the task in the background.
  FlutterForegroundTask.setTaskHandler(MyTaskHandler());
}
cho-4 commented 1 month ago

@Dev-hwang

Yes I added it

`@pragma('vm:entry-point') void startCallback() { FlutterForegroundTask.setTaskHandler(MyTaskHandler()); }

void main() { widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); FlutterForegroundTask.initCommunicationPort(); runApp(const MyApp()); }`

Dev-hwang commented 1 month ago

@cho-4

Please check whether notificationText is updated with the app I provided.

https://drive.google.com/file/d/1G7RRp5X6MLIXQww3HRCp0nRlEBtcgTsH/view?usp=sharing

this example, noficiationText is updated every 6 minutes.

Dev-hwang commented 1 month ago

You can also try the following:

goto android/app/proguard-rules.pro

add keep class flutter_foreground_task

-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.**  { *; }
-keep class io.flutter.util.**  { *; }
-keep class io.flutter.view.**  { *; }
-keep class io.flutter.**  { *; }
-keep class io.flutter.plugins.**  { *; }
-dontwarn io.flutter.embedding.**

-keep class com.pravera.flutter_foreground_task.** { *; }
Dev-hwang commented 1 month ago

@cho-4

Any news on the results?

cho-4 commented 1 month ago

@Dev-hwang I'm sorry for the late reply due to personal circumstances.

I think I didn't use taskhandler in the right place. It worked fine because I used it in the right place.

Thank you for your kindness in the meantime

Dev-hwang commented 1 month ago

ok. if you have any other issues or questions, create new issue at any time.