-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[android_alarm_manager] migrate to the V2 Android embedding #2193
Conversation
@matthew-carroll is there anything else here that I missed that's specific to background execution? Also, do we still think |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now this plugin has no unit tests or integration tests. I think that this android_alarm_manager should be straightforward to integration test using the e2e plugin. Would you be open to adding that?
I wrote some example tests that I think should work as a starting point. Feel free to copy these into the PR.
Note: these tests do not pass for me when I run them under flutter drive
(the alarm manager doesn't seem to trigger), and I'm not sure why. Maybe the alarm manager only works when the app is in the background?
final AndroidAlarmManagerPlugin plugin = new AndroidAlarmManagerPlugin(); | ||
// Listen for FlutterView destruction so that this plugin can move itself | ||
// to background mode. | ||
registrar.addViewDestroyListener(plugin); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bkonyi do you know what the analogous behavior of this plugin is when using the new embedding? I assume that what this plugin is doing when the view is destroyed is important, right? If so, we won't be able to act on view destruction, but plugins will now have the opportunity to take action when the activity disappears, which is probably what this view destruction really signified in the first place...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, looking at this code now I'm uncertain as to what it even accomplishes. It appears that on view destruction we try and store the view in AlarmService
... but we also create a view in AlarmService.startBackgroundIsolate
. I think we're safe to remove this... I'll try and see if it causes any issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good. It just needs tests like @collinjackson pointed out.
@@ -1,3 +1,9 @@ | |||
## 0.4.5 | |||
* Add support for Flutter Android embedding V2 | |||
* Called WidgetsFlutterBinding.ensureInitialized() in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this hurts, but in general we've decided that this is the responsibility of the plugin user, not author.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point. I can remove this from the initialize method, but is there any reason why we're leaving this up to the plugin user?
.../android/src/main/java/io/flutter/plugins/androidalarmmanager/AndroidAlarmManagerPlugin.java
Outdated
Show resolved
Hide resolved
final AndroidAlarmManagerPlugin plugin = new AndroidAlarmManagerPlugin(); | ||
// Listen for FlutterView destruction so that this plugin can move itself | ||
// to background mode. | ||
registrar.addViewDestroyListener(plugin); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
I'm definitely open to adding tests if we can show they're not going to be extremely flaky. The
These tests run on an actual device, correct? There might be an issue with the phone being in doze mode which results in background events being delayed until the device is in a more "active" state to conserve battery life. |
@collinjackson it looks like driver tests don't work correctly when additional isolates are spawned (see flutter/flutter#24703). It looks like isolates are being paused on start and the driver only resumes the main isolate, regardless of how the test is launched (both |
@@ -152,15 +152,6 @@ public static void setCallbackDispatcher(Context context, long callbackHandle) { | |||
prefs.edit().putLong(CALLBACK_HANDLE_KEY, callbackHandle).apply(); | |||
} | |||
|
|||
public static boolean setBackgroundFlutterView(FlutterNativeView view) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Notice that this isn't really setting a View
, it's setting a FlutterNativeView
, which is now essentially represented by FlutterEngine
. Are you sure this method can be deleted? Where is this behavior handled now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is an artifact from the original implementation of this plugin when we were running all callbacks on the main isolate, which meant we had to be able to handle the case when the application was moved from foreground to background. This plugin now instead starts a background isolate immediately when AndroidAlarmManager.initialize
is invoked or when the AlarmService
is started via an Intent
, and this isolate is kept alive until the service shuts down.
.../android/src/main/java/io/flutter/plugins/androidalarmmanager/AndroidAlarmManagerPlugin.java
Show resolved
Hide resolved
The Gradle e2e method doesn't use Flutter driver, so I'm surprised it doesn't work. I wonder if @vishna's workaround in flutter/flutter#24703 (comment) would be helpful here. |
Disregard, for some reason the background callback dispatcher wasn't calling However, I am still running into some issues with plugins seemingly not being recognized when running tests. It looks like the alarms are firing correctly but we're failing to write the count to the temporary file. We were swallowing the exception before, but I'm seeing this failure:
I'm also seeing this warning in the logs: I've tried cleaning the project and re-running and haven't had any luck. The |
...le/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java
Show resolved
Hide resolved
@bkonyi, @collinjackson, anything blocking this PR currently? |
Ah sorry for the delay. I think the migration is mostly done, but I'll need to upload my most recent changes. However, as discussed with @matthew-carroll and @mklim we're going to delay publishing this until the next stable is released to handle registering plugins with the background isolate automatically. |
908cd1d
to
010b7ea
Compare
import java.util.concurrent.CountDownLatch; | ||
import java.util.concurrent.atomic.AtomicBoolean; | ||
|
||
public class BackgroundExecutionContext implements MethodCallHandler { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may want to avoid the term Context
on Android when not referring to an Android Context
. It might be confusing for Android devs. Another name that might be more descriptive for this object could be FlutterBackgroundExecutor
Also, can you add an appropriate class javadoc?
...alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AlarmService.java
Show resolved
Hide resolved
sBackgroundFlutterView.runFromBundle(args); | ||
sPluginRegistrantCallback.registerWith(sBackgroundFlutterView.getPluginRegistry()); | ||
} | ||
assert (backgroundExecutionContext == null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assertions won't have any effect in release mode. In fact, I'm not even sure Android supports assert()
at all. What should the behavior be if this happens in a real app?
} | ||
|
||
/** | ||
* Called once the Dart isolate ({@code sBackgroundFlutterView}) has finished initializing. | ||
* Called once the Dart isolate ({@code backgroundFlutterView}) has finished initializing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is backgroundFlutterView
still the correct concept? There shouldn't be any "views" associated with background execution, I don't think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, this was an oversight. Thought I had caught all the instances of backgroundFlutterView
in the code base... will remove.
* | ||
* <p>Invoked by {@link AndroidAlarmManagerPlugin} when it receives the {@code | ||
* AlarmService.initialized} message. Processes all alarm events that came in while the isolate | ||
* was starting. | ||
*/ | ||
// TODO(mattcarroll): consider making this method package private | ||
public static void onInitialized() { | ||
static void onInitialized() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we have a style pattern of placing some kind of commented identifier before package private members, e.g., /* package */
. Would you mind adding one here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I wasn't aware. I'll go ahead and do that.
private Object initializationLock = new Object(); | ||
private MethodChannel alarmManagerPluginChannel; | ||
|
||
private static AndroidAlarmManagerPlugin singleton; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, in the embedding I always place static members above instance members, and other places where I've created singletons I do so by calling it instance
. Up to you if you'd like to replicate that here.
...android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java
Outdated
Show resolved
Hide resolved
|
||
private AtomicBoolean isIsolateRunning = new AtomicBoolean(false); | ||
|
||
private static PluginRegistrantCallback pluginRegistrantCallback; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you'd like to match the embedding, all static members are placed above instance members.
|
||
private static PluginRegistrantCallback pluginRegistrantCallback; | ||
|
||
public static void setPluginRegistrant(PluginRegistrantCallback callback) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a javadoc for this method? It's probably a good idea to point out when this is expected to be called, and also what kind of consequences might occur by failing to set this at the appropriate time.
return; | ||
} | ||
|
||
FlutterMain.ensureInitializationComplete(context, null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On master this call should no longer be needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still required for apps using the V1 embedding?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think so. In short, if a Flutter experience is based on a FlutterEngine
then that call is not needed. But if it's based on FlutterNativeView
then it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with possibly one remaining question that I left.
This plugin is relatively complicated compared to others, and delves into areas of Android that are very difficult to reason about, so I'll just add that beyond an LGTM, I encourage us to exercise this plugin wherever, whenever, and however we can. If any bugs exist, we're likely to only realize it with a stacktrace.
FYI, I've got changes addressing your earlier comments that are a WIP but I won't get to them until I'm physically in the office next week (I'm not setup for Android dev on my MacBook at the moment). I'll do my best to fix up documentation to make this plugin's behavior as clear as possible. I'm wondering what would be the best way to exercise this plugin other than the rather simplistic tests this PR adds. If we had more official plugins supporting background execution, I'd imagine that looking into having a dedicated device running applications triggering background execution events would be something worth doing, but other than that I'm not sure what we can do. |
@collinjackson it looks like the tests are failing, but I'm not sure why... is the test being run with |
Seems like the error on firebase test lab is
|
So it looks like the timeout on Firebase is too aggressive for the e2e test to pass on API 28+ and we timeout. If you look at the logcat output, we do see an @collinjackson @amirh, any suggestions? |
I'm in favor of longer (5m?) timeouts to reduce flakiness if that actually causes the test to pass. We could also make the timeouts configurable per plugin but maybe that's overthinking it. |
Finally got all the checks passing... Any objections to me landing this? |
Really looking forward to the publication of this update! |
Description
Minimum work to migrate to the Android v2 embedding.
Related Issues
flutter/flutter#41830
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]
). This will ensure a smooth and quick review process.///
).flutter analyze
) does not report any problems on my PR.Breaking Change
Does your PR require plugin users to manually update their apps to accommodate your change?