Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v2 Android embedder plugins #145

Closed
blackraven96 opened this issue Jan 3, 2020 · 37 comments
Closed

v2 Android embedder plugins #145

blackraven96 opened this issue Jan 3, 2020 · 37 comments
Assignees
Labels

Comments

@blackraven96
Copy link

Hi,

Is it possible to update your plugin for being compatible with v2 Android embedder plugins ?

Thx.

@ryanheise ryanheise self-assigned this Jan 4, 2020
@ryanheise
Copy link
Owner

I believe it is possible, although it would be difficult at this stage.

The migration guide is limited, the Flutter team has so far updated only 2 of its 24 1st class plugins to demonstrate how to support v2, and unfortunately neither of those 2 happen to serve as demonstrative examples for plugins with background tasks.

As such, I would prefer to wait for at least one of their 1st class plugins to be updated that supports background tasks. e.g. android_alarm_manager.

Although, I would welcome a pull request from anyone who is motivated to figure it out from the API documentation.

There also may be a way to support legacy plugins as is (see #138) although again the documentation is a bit lacking at this stage.

@shubham-cueclad
Copy link

well than it seems i will have to wait for udates..Thanks a lot you were pretty supportive towards issues thanks again and all the best

@xster
Copy link

xster commented Jan 9, 2020

https://github.com/flutter/flutter/projects/59 is the first party migration state. Most of them are updated but you're right in that android_alarm_manager is the slight exception. We're still currently working on the set of best practices to recommend for users running in headless/background modes (flutter/flutter#32164).

@MickaelHrndz
Copy link

Does this mean that I can't use audio_service and android_alarm_manager together for now ?
In the same app, they work when used by themselves, but when I use audio_service in a background alarm and it goes off, this exception is thrown :

[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: MissingPluginException(No implementation found for method ready on channel ryanheise.com/audioServiceBackground)
E/flutter ( 7314): #0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:154:7)
E/flutter ( 7314): <asynchronous suspension>
E/flutter ( 7314): #1      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:329:12)
E/flutter ( 7314): #2      AudioServiceBackground.run (package:audio_service/audio_service.dart:813:30)
E/flutter ( 7314): #3      _AsyncAwaitCompleter.start (dart:async-patch/async_patch.dart:47:6)
E/flutter ( 7314): #4      AudioServiceBackground.run (package:audio_service/audio_service.dart:698:26)
...

The android_alarm_manager README says it supports v2 embedding with Flutter >= 1.12. I am on v1.13.6.
Is there a way to make it work while waiting for the package updates ?
Is this even related or should I open a new issue ?

@ryanheise
Copy link
Owner

I believe it is related to this issue, yes. Currently, it seems that those who created their projects pre-1.12 are having no issues (like myself), while those who created their projects post-1.12 are facing the problem. So the workaround for now is to use an old project template for your project.

I would ideally like documentation from the Flutter team since time is too short to spend figuring undocumented things out, but if that documentation doesn't come out within the next two weeks (or sooner) I will begin those efforts.

In terms of attracting more attention to this issue, these are the GitHub issues and Medium articles where I have left a comment already:

flutter/flutter#32164
flutter/flutter#47153
https://medium.com/flutter/executing-dart-in-the-background-with-flutter-plugins-and-geofencing-2b3e40a1a124

Simply upvoting/clapping those comments may help increase visibility.

Torlinone pushed a commit to Torlinone/simple_player that referenced this issue Jan 18, 2020
@MickaelHrndz
Copy link

Thank you for your quick answer, I will try with an old project.
I upvoted and clapped.

@MickaelHrndz
Copy link

I tried with a Flutter 1.5.4 project, but unfortunately it didn't work.
PS : I am opening an unrelated issue on just_audio

@ryanheise
Copy link
Owner

Just to update this issue, I will have a crack at a v2 implementation this weekend.

@ryanheise
Copy link
Owner

I've just committed a v2 implementation to Git. Hopefully this doesn't break projects still using the old project template, but I would appreciate some feedback if anything is broken for both new and old-style projects.

@rohansohonee
Copy link
Contributor

Hi @ryanheise

To Reproduce
Steps to reproduce the behavior:

  • Run the example project. (latest git commit)
  • Open the app.
  • Close the app using back button.
  • Leak canary will generate a report.
┬───
│ GC Root: Global variable in native code
│
├─ io.flutter.embedding.engine.FlutterJNI class
│    Leaking: NO (a class is never leaking)
│    ↓ static FlutterJNI.asyncWaitForVsyncDelegate
│                        ~~~~~~~~~~~~~~~~~~~~~~~~~
├─ io.flutter.view.VsyncWaiter$1 instance
│    Leaking: UNKNOWN
│    Anonymous class implementing io.flutter.embedding.engine.FlutterJNI$AsyncWaitForVsyncDelegate
│    ↓ VsyncWaiter$1.this$0
│                    ~~~~~~
├─ io.flutter.view.VsyncWaiter instance
│    Leaking: UNKNOWN
│    ↓ VsyncWaiter.windowManager
│                  ~~~~~~~~~~~~~
├─ android.view.WindowManagerImpl instance
│    Leaking: UNKNOWN
│    ↓ WindowManagerImpl.mContext
│                        ~~~~~~~~
├─ android.app.ContextImpl instance
│    Leaking: UNKNOWN
│    ↓ ContextImpl.mAutofillClient
│                  ~~~~~~~~~~~~~~~
╰→ com.ryanheise.audioserviceexample.MainActivity instance
     Leaking: YES (ObjectWatcher was watching this because com.ryanheise.audioserviceexample.MainActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
     key = 188e9832-ade8-4c58-8094-5545e59ae865
     watchDurationMillis = 5204
     retainedDurationMillis = 199

METADATA

Build.VERSION.SDK_INT: 27
Build.MANUFACTURER: 10or
LeakCanary version: 2.1
App process name: com.ryanheise.audioserviceexample
Analysis duration: 12492 ms

@ryanheise
Copy link
Owner

Thanks, @rohansohonee

I'm looking into it.

@MickaelHrndz
Copy link

I tried the v2 implementation and the app works like before. However I am still having the issue described in my previous message. I tried in a new empty project and with a different alarm plugin (workmanager). A MissingPluginException is thrown no matter what.

@rohansohonee
Copy link
Contributor

Hi @MickaelHrndz

Visit Upgrading-pre-1.12-Android-projects and follow the Full-Flutter app migration steps.
Also use android_alarm_manager plugin as it has v2 support.

MissingPluginException might be fixed if you add this to your MainActivity.java:

public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
   GeneratedPluginRegistrant.registerWith(flutterEngine);
}

Hope this is helpful.

@ryanheise
Copy link
Owner

Also I believe this can be fixed by using a sufficiently recent version of Flutter where plugin registration will be done automatically through reflection. If it's not in stable now, it should be soon (or you can try beta).

@MickaelHrndz
Copy link

Hi @rohansohonee, thank you for the answer. I followed the guide but it didn't solve the issue.
@ryanheise thx for the answer as well. I tried on the latest stable & beta versions but still no luck.
To be clear, this is the line raising the error in my code (only when used in the alarm callback) :

AudioServiceBackground.run(() => PlayerBackgroundTask());

And this is the code I use in the new testing project :

main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await AndroidAlarmManager.initialize();
  runApp(MyApp());
  await AndroidAlarmManager.periodic(const Duration(seconds: 10), 0, backgroundTaskEntrypoint);
}

void backgroundTaskEntrypoint() => AudioServiceBackground.run(() => MyBackgroundTask());

class MyBackgroundTask extends BackgroundAudioTask {
  @override
  Future<void> onStart() async {}
  @override
  void onStop() {}
  @override
  void onPlay() {}
  @override
  void onPause() {}
  @override
  void onClick(MediaButton button) {}
}

The exception thrown is identical in both projects :

MissingPluginException(No implementation found for method ready on channel ryanheise.com/audioServiceBackground)

@dkobia
Copy link

dkobia commented Jan 29, 2020

@MickaelHrndz confirmed on both dev and beta channels.

@ryanheise
Copy link
Owner

@dkobia @MickaelHrndz can you reproduce this on the audio_service example? I was not able to.

I added workmanager: ^0.2.0 to pubspec.yaml, ran the example and pressed play, and it did not complain about a missing plugin. As mentioned, I am using the latest update on the beta channel. Please try the same modification to the audio_service example on your own end and let me know if you run into the problem.

If it works when modifying the example in this way but does not work in your own project, then you can compare the two projects to spot the difference and maybe re-check everything in the v2 migration guide:

https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects

I am not sure what specifically is the cause, but the most important thing is to add this to your manifest:

<meta-data
    android:name="flutterEmbedding"
    android:value="2" />

But there are other more subtle things such as to remove this:

<meta-data
                android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
                android:value="true" />

The problem is that this references the old (pre-v2) class name and may drag in classes that can cause problems.

@MickaelHrndz
Copy link

Using the audio_service example project, with the code :

main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await AndroidAlarmManager.initialize();
  runApp(MyApp());
  await AndroidAlarmManager.periodic(const Duration(seconds: 1), 0, backgroundTaskEntrypoint);
}

void backgroundTaskEntrypoint() {
  print("###### 1 ######");
  AudioServiceBackground.run(() => MyBackgroundTask());
} 

class MyBackgroundTask extends BackgroundAudioTask {
  @override
  Future<void> onStart() async {
    print("######  2  ######");
  }
  @override
  void onStop() {}
  @override
  void onPlay() {}
  @override
  void onPause() {}
  @override
  void onClick(MediaButton button) {}
}

If I don't add the android_alarm_manager service and receivers in the manifest, # 1 # and # 2 # are never printed and no error is thrown. "AlarmService started!" is still printed though.

If I do, # 1 # is printed but the same error is thrown by the same line, and therefore # 2 # is not printed.

So I guess the issue really stems from android_alarm_manager ?

@ryanheise
Copy link
Owner

@MickaelHrndz I didn't realise you were using audio_service in that way, but this is definitely not supported. i.e. Your backgroundTaskEntrypoint looks like the entrypoint for AudioService.start but you are using it as the entrypoint for android_alarm_manager. Different plugins do not understand each other's entrypoints, so you must create a dedicated entrypoint for android_alarm_manager and a dedicated entrypoint for audio_service.

Stepping back and looking at what you're trying to do, it seems you want to periodically play a sound in the background every second. There are a few problems with using audio_service to do this.

  1. audio_service wasn't designed to be started and stopped with such frequency because it's a foreground service. It will show a media-style notification that itself takes some time to appear and disappear. If your audio is so short that it can be played once per second, then there is no need for audio_service anyway, you should be able to get away with just playing the audio without wrapping that audio in audio_service.
  2. If you tried to rectify the problem where you never actually called AudioService.start, the problem is that this plugin is designed such that AudioService.start must be started from the UI. Looking at your use case, I guess you'd probably want to start it within the periodic callback for the alarm manager, but since that is not the UI isolate, I doubt it would work. If it doesn't work, it's also not a priority for me to address that use case (although I might consider a pull request if there is a clean way to support it).

@ryanheise
Copy link
Owner

@rohansohonee regarding the memory leak, this appears to be a bug in the Flutter engine (see flutter/engine#16204) but if there is a workaround before that pull request lands, I'll implement it.

@ryanheise
Copy link
Owner

ryanheise commented Jan 30, 2020

@rohansohonee Here is a workaround for the leak:

public class MainActivity extends FlutterActivity {
  @Override
  public FlutterEngine provideFlutterEngine(Context context) {
    // Instantiate a FlutterEngine.
    FlutterEngine flutterEngine = new FlutterEngine(context.getApplicationContext());

    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.getDartExecutor().executeDartEntrypoint(
      DartExecutor.DartEntrypoint.createDefault()
    );

    return flutterEngine;
  }
}

What this does is override the instantiation of the main activity's FlutterEngine because the one that the framework instantiates for us retains a reference to your main activity. The code above works around this by passing in the application context rather than the activity instance to the constructor parameter of FlutterEngine. You can also find another way to do this here (but make sure you change the parameter to FlutterEngine in the same way as above):

https://flutter.dev/docs/development/add-to-app/android/add-flutter-screen#step-3-optional-use-a-cached-flutterengine

This workaround should not be needed once they fix it upstream.

If that works for you, I'll likely publish a new release with the v2 implementation, and can sort out any remaining issues after that release.

@MickaelHrndz
Copy link

@ryanheise The 1s delay was to avoid waiting while testing. I am building a radio app that lets you optionally set an alarm clock to wake up to it. That's why I need to use audio_service in android_alarm_manager.
If I try to use AudioService inside the alarm callback, the error thrown is the same, except for the method :

No implementation found for method isRunning on channel ryanheise.com/audioService

What would I use if not AudioServiceBackground.run(() => PlayerBackgroundTask(play)); ?

@rohansohonee
Copy link
Contributor

Hi @MickaelHrndz

You might have to implement your own media notification.
For audio playback you can use just_audio plugin.
This solution may not be the best but can work for now in my opinion.

@MickaelHrndz
Copy link

MickaelHrndz commented Jan 30, 2020

@rohansohonee I guess but I'm already using just_audio wrapped with audio_service and it is working just fine. I don't want to rewrite my code and a whole plugin, just to run into the same issue I have here. But yeah, just_audio is working in the alarm callback.

If you meant a different notification specifically for the alarm, it might actually work and be good enough. I will give it a try, thanks for the idea.

@rohansohonee
Copy link
Contributor

Hey guys just letting you all know a new version of Flutter has been pushed on stable channel.
Please do upgrade flutter and post your report.

@rohansohonee
Copy link
Contributor

@ryanheise
I will wait until they fix it in upstream.

@ryanheise
Copy link
Owner

@MickaelHrndz OK, I have a clearer picture of your use case now. That is not actually allowed on Android due to background service limitations:

https://developer.android.com/about/versions/oreo/background.html#services

So the only way you can really achieve what you're trying to achieve is to keep the audio service running the whole time in the background, effectively on "pause", and start playing at the scheduled time, and then you would essentially do away with alarm manager. I know this is not an ideal solution since it will unnecessarily use battery when it could be sleeping. To implement this properly, you may need to get your hands dirty with some Android programming and research whether there is a certain way to build this use case on Android.

@MickaelHrndz
Copy link

@ryanheise Interesting. Is there a way to hide the audio_service notification ?

I can make it work with another notification from flutter_local_notifications, but the player doesn't stop when the alarm in cancelled.
I need to either detect from the callback when the application is started to stop its player, or being able to call something like AudioPlayer.disposeAll(). This might be related to the fact that a player isn't stopped / disposed when you restart the app (with shift +r), resulting in multiple audios being played. Should I open a new issue on the just_audio repo ?

@ryanheise
Copy link
Owner

@MickaelHrndz You can't hide the notification. This is another requirement imposed by Android when you start a foreground service. You have to let the user know that you're consuming resources in the background, and this notification is the way this is done.

I don't know a way to hook into hot restart and dispose resources.

@rohansohonee
Copy link
Contributor

@MickaelHrndz

I think i might have a solution for this use case. Follow these steps:

  1. When your alarm fires you will enter backgroundTaskEntryPoint().
  2. Inside backgroundTaskEntryPoint() you will need to launch your FlutterActivity and pass some parameter which tell's that the alarm was fired (this will allow you to check in dart code that alarm has fired and you need to play audio).
  3. Once the FlutterActivity starts and app get's launched you can start the audio_service.

You may need to do some android coding to launch an Activity from the background isolate. But once you manage to launch the activity along with some parameter in the activity's intent you should be easily able to start the audio_service without any issues.
Hope this is helpful.

@ryanheise
Copy link
Owner

@MickaelHrndz @dkobia Are either of you still getting the MissingPluginException? If not, I will close this issue.

@MickaelHrndz
Copy link

@rohansohonee That's an even better idea, thank you. I tried with both the flutter_appavailability and a custom method channel, but I still get this dreaded MissingPluginException. I would think I can't interact with the platform code at all inside the alarm callback, if it weren't for just_audio actually working. I'm pretty clueless.

@ryanheise since the ones I get are not really related to audio_service itself, it's fine by me. Thank you again for your help.

@rohansohonee
Copy link
Contributor

@MickaelHrndz
The reason flutter_appavailability may not be working is:

  1. It uses registrar.activity during registration. It should instead use registrar and then get the applicationContext from registrar i.e registrar.getApplicationContext. (Please inform the author of the plugin to update)
  2. The v2 embedding might also be a reason. Please ensure you have performed flutter clean, you are on latest flutter stable channel and you have performed plugin registration.

IMPORTANT

<meta-data
    android:name="flutterEmbedding"
    android:value="2" />

and your MainActivity.java

public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
   GeneratedPluginRegistrant.registerWith(flutterEngine);
}

If you still keep getting MissingPluginException then take this issue up with the flutter team.

NOTE: doing a flutter clean, running pub get packages or upgrading flutter can sometimes fix these kind of issues.

@rohansohonee
Copy link
Contributor

@ryanheise
I believe this issue can be closed now.

@ryanheise
Copy link
Owner

Closing.

Note: this is feature has now been published as release 0.6.0.

@dkobia
Copy link

dkobia commented Jan 30, 2020

@ryanheise yes thanks the issue was resolved. Interestingly the MissingPluginException was caused by another audio plugin that seems to break v2 reflection altogether -- https://pub.dev/packages/audiofileplayer -- not sure how or why.

@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs, or use StackOverflow if you need help with audio_service.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 20, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

7 participants