[quick_actions_android] Return null from getLaunchAction when no activity is attached - #12331
[quick_actions_android] Return null from getLaunchAction when no activity is attached#12331Sembauke wants to merge 3 commits into
Conversation
…vity is attached getLaunchAction threw quick_action_getlaunchaction_no_activity whenever the plugin had no attached activity, which made QuickActionsAndroid.initialize throw, since it calls getLaunchAction unconditionally. An engine can legitimately run without an activity, for example a cached engine warmed up by a background service, so callers had no way to avoid this other than catching the error code around initialize. The throw also provided no recovery value: initialize registers the Dart handler before calling getLaunchAction, and any action that arrives later is reported through the plugin's onNewIntent listener once an activity attaches. A warning is logged so a misconfigured launcher activity stays diagnosable. Fixes flutter/flutter#190348
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request modifies the Android quick_actions plugin to return null and log a message instead of throwing a FlutterError when getLaunchAction is called without an attached activity. It also adds a unit test to verify this behavior. The review feedback suggests downgrading the log level from warning to debug to prevent misleading logs during normal execution, and recommends using doReturn().when() instead of when().thenReturn() for stubbing the Mockito spy in the test.
| // up by a background service. Nothing launched the app in that case, so there is no launch | ||
| // action to report. Any action that arrives later is reported through the plugin's | ||
| // onNewIntent listener once an activity attaches. | ||
| Log.w(TAG, "There is no activity available when getting the launch action."); |
There was a problem hiding this comment.
Since running the engine without an attached activity is a legitimate and expected use case (e.g., when warmed up by a background service), logging this as a warning (Log.w) can be misleading. It may cause developers to think there is an issue with their configuration when everything is operating normally. Consider using debug logging (Log.d) instead to keep the production logs clean while still allowing the state to be diagnosable during development.
| Log.w(TAG, "There is no activity available when getting the launch action."); | |
| Log.d(TAG, "There is no activity available when getting the launch action."); |
There was a problem hiding this comment.
Agreed, done in 37d79d0. A media app can hit this on every headless engine start, so warning level would be recurring noise for normal operation, and a genuinely misconfigured launcher activity is something you would be debugging anyway, where Log.d is visible.
| // Build.VERSION.SDK_INT is 0 in unit tests, so the version check is stubbed to make sure the | ||
| // null activity, and not the unsupported version, is what is being exercised here. | ||
| final QuickActions quickActions = spy(new QuickActions(mock(Context.class))); | ||
| when(quickActions.isVersionAllowed()).thenReturn(true); |
There was a problem hiding this comment.
When stubbing methods on a Mockito spy, it is highly recommended to use doReturn().when() instead of when().thenReturn(). Using when().thenReturn() actually invokes the real method on the spy, which can lead to unexpected side effects or NullPointerExceptions if the real method relies on uninitialized state. Although isVersionAllowed() is safe to call here, using doReturn is a safer and more idiomatic practice for spies.
| when(quickActions.isVersionAllowed()).thenReturn(true); | |
| org.mockito.Mockito.doReturn(true).when(quickActions).isVersionAllowed(); |
There was a problem hiding this comment.
Done in 37d79d0, using a static import to match the existing import style in this file rather than the fully-qualified call.
I re-verified the test is still a real regression test after the change, since doReturn alters stubbing semantics: against the unmodified QuickActions.java it still fails with io.flutter.plugins.quickactions.FlutterError, and passes with the fix.
|
@googlebot I signed it! |
Logs at debug level rather than warning, since running without an attached activity is an expected state and would otherwise be noisy on every headless engine start. Uses doReturn().when() for the spy stub so the real method is not invoked while stubbing.
Reverts the version bump and CHANGELOG entry, leaving the release decision to the package maintainers.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request modifies the Android QuickActions plugin to return null instead of throwing a FlutterError when the activity is null during getLaunchAction(), logging a debug message instead. A corresponding unit test was added to verify this behavior. The review feedback suggests changing the log level from debug (Log.d) to warning (Log.w) to ensure that a missing activity remains diagnosable in logcat.
| // up by a background service. Nothing launched the app in that case, so there is no launch | ||
| // action to report. Any action that arrives later is reported through the plugin's | ||
| // onNewIntent listener once an activity attaches. | ||
| Log.d(TAG, "There is no activity available when getting the launch action."); |
There was a problem hiding this comment.
The PR description mentions that this change logs a warning so a genuinely misconfigured launcher activity stays diagnosable in logcat. However, the implementation uses Log.d (debug level) instead of Log.w (warning level). Using Log.w ensures that this potential misconfiguration is logged as a warning, making it easier to diagnose in production/logcat.
| Log.d(TAG, "There is no activity available when getting the launch action."); | |
| Log.w(TAG, "There is no activity available when getting the launch action."); |
|
Thank you for your contribution! Because of the volume of PRs we receive, we require that new contributors use our checklist to guide them through critical steps in creating a Flutter PR. This PR's description is using an alternate (possibly AI-generated) checklist that does not follow our process. Please edit the PR description to add the correct checklist, then ensure that you have completed all of the steps. Once you've done that, please mark the PR as ready for review. If you need help, consider asking for advice on the #hackers-new channel on Discord. |
There was a problem hiding this comment.
Code Review
This pull request modifies QuickActions.java to return null and log a debug message instead of throwing a FlutterError when there is no activity available during getLaunchAction(). Additionally, a unit test getLaunchAction_noActivity_returnsNull has been added to QuickActionsTest.java to verify this behavior. I have no feedback to provide.
getLaunchActionthrewquick_action_getlaunchaction_no_activitywhen the plugin had no attached activity.QuickActionsAndroid.initializecallsgetLaunchActionunconditionally, soinitializethrew aPlatformExceptionthat callers could not prevent.An engine can legitimately run without an attached activity. A cached engine warmed up by a background service is the common case:
audio_servicesupplies a cached engine to its activity and also warms up that same engine fromAudioService.onCreate, somain()runs with no activity when a media button or Android Auto starts the service.This returns
nullin that case instead, and logs at debug level. The throw provided no recovery value:initializeregisters the Dart handler before it callsgetLaunchAction, so the handler is already installed when the exception is thrown.QuickActionsPlugin.onAttachedToActivitycallsonNewIntent(activity.getIntent()), which reports the shortcut throughAndroidQuickActionsFlutterApi.launchAction.nullis also already the return value for the other "cannot answer" case in this method, on the line directly above (if (!isVersionAllowed())).Verified on an Android 12 emulator by pointing an app at this branch and starting its audio service with no UI: no exception, and a shortcut tap afterwards still routed correctly. A cold start from a shortcut still returns the action, so the activity-present path is unchanged. The new unit test fails with
io.flutter.plugins.quickactions.FlutterErroragainst the unmodified file.Fixes flutter/flutter#190348
Pre-Review Checklist
[shared_preferences]///).Footnotes
Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2