SayWut / flutter_foreground_service_plugin

MIT License
12 stars 12 forks source link

Using other plugins inside service? #7

Closed sukhcha-in closed 3 years ago

sukhcha-in commented 3 years ago

Is there any way to use other plugins inside foreground service?

I'm trying to save how many times the service ran, but unfortunately get_storage plugin doesn't work.

I've also tried foreground_service plugin. Same issue with that one. Is there some kind of limitations?

import 'package:flutter/material.dart';
import 'package:flutter_foreground_service_plugin/flutter_foreground_service_plugin.dart';
import 'package:get_storage/get_storage.dart';

void main() async {
  await GetStorage.init();
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Builder(
          builder: (context) {
            return Center(
              child: Column(
                children: [
                  TextButton(
                    child: Text('Start service'),
                    onPressed: () async {
                      await FlutterForegroundServicePlugin
                          .startForegroundService(
                        notificationContent: NotificationContent(
                          iconName: 'ic_launcher',
                          titleText: 'Title Text',
                          color: Colors.green,
                          priority: NotificationPriority.high,
                        ),
                        notificationChannelContent: NotificationChannelContent(
                          id: 'some_id',
                          nameText: 'settings title',
                          descriptionText: 'settings description text',
                        ),
                        isStartOnBoot: false,
                      );
                    },
                  ),
                  TextButton(
                    child: Text('Stop service'),
                    onPressed: () async {
                      await FlutterForegroundServicePlugin
                          .stopForegroundService();
                    },
                  ),
                  TextButton(
                    child: Text('Is service running'),
                    onPressed: () async {
                      var isRunning = await FlutterForegroundServicePlugin
                          .isForegroundServiceRunning();
                      print(isRunning);
                      var snackbar = SnackBar(
                        content: Text('$isRunning'),
                        duration: Duration(milliseconds: 500),
                      );
                      Scaffold.of(context).showSnackBar(snackbar);
                    },
                  ),
                  TextButton(
                    child: Text('Start task'),
                    onPressed: () async {
                      await FlutterForegroundServicePlugin.startPeriodicTask(
                        periodicTaskFun: periodicTaskFun,
                        period: const Duration(seconds: 5),
                      );
                    },
                  ),
                  TextButton(
                    child: Text('Stop task'),
                    onPressed: () async {
                      await FlutterForegroundServicePlugin.stopPeriodicTask();
                    },
                  ),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

void periodicTaskFun() {
  int ran = GetStorage().read('ran') ?? 0;
  GetStorage().write('ran', ran + 1);

  FlutterForegroundServicePlugin.executeTask(() async {
    // this will refresh the notification content each time the task is fire
    // if you want to refresh the notification content too each time
    // so don't set a low period duretion because android isn't handling it very well
    await FlutterForegroundServicePlugin.refreshForegroundServiceContent(
      notificationContent: NotificationContent(
        iconName: 'ic_launcher',
        titleText: 'Task ran',
        bodyText: '${DateTime.now()}',
        subText: '${ran.toString()} times',
        color: Colors.blue,
      ),
    );
    print('Task ran: ${ran.toString()} times');
  });
}
SayWut commented 3 years ago

I explained it in other issue

In flutter to send data between the android OS and the dart code you need to work with channels which works over an engine. When the app is starts there is a main engine that created with it, but to make the task work when the flutter app is closed I need to create a different engine which isn't communicate with the main engine. In the end it means that you need to treat the task function like another flutter app, you need to initialize everything again in the task function (in the executeTask fun)

sukhcha-in commented 3 years ago

In flutter to send data between the android OS and the dart code you need to work with channels which works over an engine. When the app is starts there is a main engine that created with it, but to make the task work when the flutter app is closed I need to create a different engine which isn't communicate with the main engine. In the end it means that you need to treat the task function like another flutter app, you need to initialize everything again in the task function (in the executeTask fun)

Okay so it means that we cannot save data to main engine instead executeTask is completely new engine that has it's own state and everything. When we stop task it destroys everything. No data will be kept.

Is there any way to provide initial data from main engine to executeTask engine?

sukhcha-in commented 3 years ago

get_storage plugin works fine but I want to use headless webview inside foreground service.

When app is running in background it works fine. But when main app is terminated there is MissingPluginException.

E/flutter ( 9122): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: MissingPluginException(No implementation found for method createHeadlessWebView on channel com.pichillilorenzo/flutter_headless_inappwebview)
E/flutter ( 9122): #0      MethodChannel._invokeMethod
package:flutter/…/services/platform_channel.dart:157
E/flutter ( 9122): <asynchronous suspension>
E/flutter ( 9122): #1      MethodChannel.invokeMethod
package:flutter/…/services/platform_channel.dart:332
E/flutter ( 9122): #2      HeadlessInAppWebView.run
package:flutter_inappwebview/src/headless_in_app_webview.dart:117
E/flutter ( 9122): #3      periodicTaskFun.<anonymous closure>
package:app/screens/fgservice.dart:120
E/flutter ( 9122): #4      periodicTaskFun.<anonymous closure>
package:app/screens/fgservice.dart:90
E/flutter ( 9122): #5      FlutterForegroundServicePlugin.executeTask.<anonymous closure>
package:flutter_foreground_service_plugin/flutter_foreground_service_plugin.dart:184
E/flutter ( 9122): #6      MethodChannel._handleAsMethodCall
package:flutter/…/services/platform_channel.dart:430
E/flutter ( 9122): #7      MethodChannel.setMethodCallHandler.<anonymous closure>
package:flutter/…/services/platform_channel.dart:383
E/flutter ( 9122): #8      _DefaultBinaryMessenger.handlePlatformMessage
package:flutter/…/services/binding.dart:283
E/flutter ( 9122): #9      _invoke3.<anonymous closure> (dart:ui/hooks.dart:280:15)
E/flutter ( 9122): #10     _rootRun (dart:async/zone.dart:1190:13)
E/flutter ( 9122): #11     _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter ( 9122): #12     _CustomZone.runGuarded (dart:async/zone.dart:997:7)
E/flutter ( 9122): #13     _invoke3 (dart:ui/hooks.dart:279:10)
E/flutter ( 9122): #14     _dispatchPlatformMessage (dart:ui/hooks.dart:154:5)

Any way to handle this?

SayWut commented 3 years ago

In flutter to send data between the android OS and the dart code you need to work with channels which works over an engine. When the app is starts there is a main engine that created with it, but to make the task work when the flutter app is closed I need to create a different engine which isn't communicate with the main engine. In the end it means that you need to treat the task function like another flutter app, you need to initialize everything again in the task function (in the executeTask fun)

Okay so it means that we cannot save data to main engine instead executeTask is completely new engine that has it's own state and everything. When we stop task it destroys everything. No data will be kept.

Is there any way to provide initial data from main engine to executeTask engine?

I am pretty sure there is a way to send some arguments but it will be basic types numeric, decimal ans string I don't think there is a good way to send something else

get_storage plugin works fine but I want to use headless webview inside foreground service.

When app is running in background it works fine. But when main app is terminated there is MissingPluginException.

E/flutter ( 9122): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: MissingPluginException(No implementation found for method createHeadlessWebView on channel com.pichillilorenzo/flutter_headless_inappwebview)
E/flutter ( 9122): #0      MethodChannel._invokeMethod
package:flutter/…/services/platform_channel.dart:157
E/flutter ( 9122): <asynchronous suspension>
E/flutter ( 9122): #1      MethodChannel.invokeMethod
package:flutter/…/services/platform_channel.dart:332
E/flutter ( 9122): #2      HeadlessInAppWebView.run
package:flutter_inappwebview/src/headless_in_app_webview.dart:117
E/flutter ( 9122): #3      periodicTaskFun.<anonymous closure>
package:app/screens/fgservice.dart:120
E/flutter ( 9122): #4      periodicTaskFun.<anonymous closure>
package:app/screens/fgservice.dart:90
E/flutter ( 9122): #5      FlutterForegroundServicePlugin.executeTask.<anonymous closure>
package:flutter_foreground_service_plugin/flutter_foreground_service_plugin.dart:184
E/flutter ( 9122): #6      MethodChannel._handleAsMethodCall
package:flutter/…/services/platform_channel.dart:430
E/flutter ( 9122): #7      MethodChannel.setMethodCallHandler.<anonymous closure>
package:flutter/…/services/platform_channel.dart:383
E/flutter ( 9122): #8      _DefaultBinaryMessenger.handlePlatformMessage
package:flutter/…/services/binding.dart:283
E/flutter ( 9122): #9      _invoke3.<anonymous closure> (dart:ui/hooks.dart:280:15)
E/flutter ( 9122): #10     _rootRun (dart:async/zone.dart:1190:13)
E/flutter ( 9122): #11     _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter ( 9122): #12     _CustomZone.runGuarded (dart:async/zone.dart:997:7)
E/flutter ( 9122): #13     _invoke3 (dart:ui/hooks.dart:279:10)
E/flutter ( 9122): #14     _dispatchPlatformMessage (dart:ui/hooks.dart:154:5)

Any way to handle this?

Can you copy your task fun to here

sukhcha-in commented 3 years ago

Can you copy your task fun to here

import 'package:flutter_inappwebview/flutter_inappwebview.dart'; //https://pub.dev/packages/flutter_inappwebview
import 'package:get_storage/get_storage.dart'; //https://pub.dev/packages/get_storage

void periodicTaskFun() {
  FlutterForegroundServicePlugin.executeTask(() async {

    //headless webview which doesn't work when app is terminated
    HeadlessInAppWebView headlessWebView = HeadlessInAppWebView(
      initialUrl: 'https://xxx.ngrok.io/api/test',
      initialOptions: InAppWebViewGroupOptions(
        crossPlatform: InAppWebViewOptions(
          debuggingEnabled: true,
        ),
      ),
      onWebViewCreated: (controller) {
        print('HeadlessInAppWebView created!');
      },
      onConsoleMessage: (controller, consoleMessage) {
        print("CONSOLE MESSAGE: " + consoleMessage.message);
      },
      onLoadStart: (controller, url) async {
        print("onLoadStart $url");
      },
      onLoadStop: (controller, url) async {
        print("onLoadStop $url");
      },
      onUpdateVisitedHistory: (InAppWebViewController controller, String url,
          bool androidIsReload) {
        print("onUpdateVisitedHistory $url");
      },
    );
    headlessWebView.run();
    //end headless webview

    // When service started
    if (GetStorage().read('started_at') == null) {
      GetStorage().write('started_at', DateTime.now().toString());
    } else {
      print(GetStorage().read('started_at'));
    }

    // save times service ran
    int ran = GetStorage().read('ran') ?? 0;
    GetStorage().write('ran', ran + 1);

    await FlutterForegroundServicePlugin.refreshForegroundServiceContent(
      notificationContent: NotificationContent(
        iconName: 'ic_launcher',
        titleText: 'Task ran',
        bodyText: '${DateTime.now()}',
        subText: '${ran.toString()} times',
        color: Colors.blue,
      ),
    );

    // headlessWebView.dispose();
    print('Task ran: ${ran.toString()} times');
  });
}
SayWut commented 3 years ago

@sukhcha-in I will check this and let you know if I find a solution

SayWut commented 3 years ago

@sukhcha-in I created a new project and I copied your code and everything runs,

try to do a clean your build of your project and launch it again flutter clean

sukhcha-in commented 3 years ago

@sukhcha-in I created a new project and I copied your code and everything runs,

try to do a clean your build of your project and launch it again flutter clean

Does it work when app is terminated? It works when app is in foreground. It works when app is in background. It doesn't work when app is terminated but foreground service is running.

SayWut commented 3 years ago

@sukhcha-in I created a new project and I copied your code and everything runs, try to do a clean your build of your project and launch it again flutter clean

Does it work when app is terminated? It works when app is in foreground. It works when app is in background. It doesn't work when app is terminated but foreground service is running.

I forgot about that part. ya it doesn't work when the app is closed.

I will look for a solution

SayWut commented 3 years ago

@sukhcha-in OK So from what I see the problem isn't in my plugin it is in the webview plugin

this is the error log:

2021-01-25 00:19:50.340 15794-15794/com.example.flutter_application_1 E/MethodChannel#com.pichillilorenzo/flutter_headless_inappwebview: Failed to handle method call
    java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object android.content.Context.getSystemService(java.lang.String)' on a null object reference
        at com.pichillilorenzo.flutter_inappwebview.InAppWebView.FlutterWebView.<init>(FlutterWebView.java:49)
        at com.pichillilorenzo.flutter_inappwebview.HeadlessInAppWebViewManager.createHeadlessWebView(HeadlessInAppWebViewManager.java:74)
        at com.pichillilorenzo.flutter_inappwebview.HeadlessInAppWebViewManager.onMethodCall(HeadlessInAppWebViewManager.java:59)
        at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:233)
        at io.flutter.embedding.engine.dart.DartMessenger.handleMessageFromDart(DartMessenger.java:85)
        at io.flutter.embedding.engine.FlutterJNI.handlePlatformMessage(FlutterJNI.java:692)
        at android.os.MessageQueue.nativePollOnce(Native Method)
        at android.os.MessageQueue.next(MessageQueue.java:336)
        at android.os.Looper.loop(Looper.java:174)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

this is the problematic code line:

DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);

as you can see the exception comes from the FlutterWebView class it try to get the DisplayManager of the app but the context is null so it throws an exception

from what I see the context of the FlutterWebView is bind to the activity of the app and because the app is dead there is no activity therefore there is no context

I hope it understandable and sorry for bad English