Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[google_maps_flutter] Overhaul lifecycle management in GoogleMapsPlugin #3213

Merged
merged 14 commits into from
Oct 29, 2020

Conversation

math1man
Copy link
Contributor

Description

Overhaul lifecycle management in GoogleMapsPlugin.

GoogleMapController is now uniformly driven by implementing DefaultLifecycleObserver. That observer is registered to a lifecycle passed via the new LifecycleProvider interface, which has 3 implementations:

  1. For v2 plugin registration, GoogleMapsPlugin implements the interface and holds the lifecycle in a field, which is itself controlled by ActivityAware methods.
  2. For v1 plugin registration, if the activity implements LifecycleOwner, it's lifecycle is used directly.
  3. For v1 plugin registration, if the activity does not implement LifecycleOwner, a proxy lifecycle is created via ActivityLifecycleCallbacks.

I'm suspicious that there may still be a bug where a GoogleMapController instance can be shared across multiple activities and therefore leak the activity and lifecycle.

Related Issues

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.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • My PR includes unit or integration tests for all changed/updated/fixed behaviors (See Contributor Guide).
  • All existing and new tests are passing.
  • I updated/added relevant documentation (doc comments with ///).
  • The analyzer (flutter analyze) does not report any problems on my PR.
  • I read and followed the Flutter Style Guide.
  • The title of the PR starts with the name of the plugin surrounded by square brackets, e.g. [shared_preferences]
  • I updated pubspec.yaml with an appropriate new version according to the pub versioning philosophy.
  • I updated CHANGELOG.md to add a description of the change.
  • I signed the CLA.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Does your PR require plugin users to manually update their apps to accommodate your change?

  • Yes, this is a breaking change (please indicate a breaking change in CHANGELOG.md and increment major revision).
  • No, this is not a breaking change.

…fecycleObserver. That observer is registered to a lifecycle passed via the new LifecycleProvider interface, which has 3 implementations:

1. For v2 plugin registration, GoogleMapsPlugin implements the interface and holds the lifecycle in a field, which is itself controlled by ActivityAware methods.
2. For v1 plugin registration, if the activity implements LifecycleOwner, it's lifecycle is used directly.
3. For v1 plugin registration, if the activity does not implement LifecycleOwner, a proxy lifecycle is created via ActivityLifecycleCallbacks.

I'm suspicious that there may still be a bug where a GoogleMapController instance can be shared across multiple activities and therefore leak the activity and lifecycle.
@google-cla google-cla bot added the cla: yes label Oct 27, 2020
@math1man math1man changed the title GoogleMapController is now uniformly driven by implementing DefaultLi… [google_maps_flutter] Overhaul lifecycle management in GoogleMapsPlugin Oct 28, 2020
Copy link
Contributor

@amirh amirh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! thanks!

Left a few minor comments.

@@ -1,3 +1,13 @@
## 1.1.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't introduce new features so should be a patch bump (1.0.5)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

pluginBinding = binding;
binding
.getPlatformViewRegistry()
.registerViewFactory(VIEW_TYPE, new GoogleMapFactory(binding.getBinaryMessenger(), this));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about creating a separate LifecycleProvider object which is just the lifecycle provider and passing that here (instead of having GoogleMapsPlugin implement LifecycleProvider). My worry is that future developers adding members to GoogleMapsPlugins will miss the fact that a reference to it is being retained by GoogleMapFactory...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, done

getApplication().registerActivityLifecycleCallbacks(this);
}
void init() {
lifecycleProvider.getLifecycle().addObserver(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure so double checking - will we get the initial notification for the current state?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, from addObserver's documentation:

The given observer will be brought to the current state of the LifecycleOwner. For example, if the LifecycleOwner is in State#STARTED state, the given observer will receive Event#ON_CREATE, Event#ON_START events

getApplication().unregisterActivityLifecycleCallbacks(this);
Lifecycle lifecycle = lifecycleProvider.getLifecycle();
if (lifecycle != null) {
lifecycle.removeObserver(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(not sure) do we need to protect against calling removeObserver twice? (here and from onDestroy)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, operation is idempotent

@@ -1,7 +1,7 @@
name: google_maps_flutter
description: A Flutter plugin for integrating Google Maps in iOS and Android applications.
homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter/google_maps_flutter
version: 1.0.4
version: 1.1.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't forget to update the version here as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

public void onStop(@NonNull LifecycleOwner owner) {
state.set(CREATED);
}
private static final class ProxyLifecycleProvider
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can you add some comments here describing that this is used make a lifecycle our of the application lifecycle calls?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}));
} else {
ProxyLifecycleProvider proxyLifecycleProvider = new ProxyLifecycleProvider(activity);
activity.getApplication().registerActivityLifecycleCallbacks(proxyLifecycleProvider);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional nit: I'd consider moving this into ProxyLifecycleProvider to keep all the hackery in one class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

VIEW_TYPE,
new GoogleMapFactory(
binding.getBinaryMessenger(),
new LifecycleProvider() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to keep an implicit reference to the outer class?

Copy link
Contributor Author

@math1man math1man Oct 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually keeps an explicit reference to the outer class, because lifecycle is a field in the outer class. I don't think that's avoidable because only the plugin itself can implement ActivityAware.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm probably missing something obvious... what fails if:

GoogleMapsPlugin holds a reference to the provider (an implementation with a lifecycle field) and sets the lifecycle when attached/detached to the activity, and we pass that provider reference to GoogleMapFactory, won't we avoid a reference from GoogleMapFactory to GoogleMapsPlugin in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could do that, but it doesn't really matter and ends up being more boiler plate. My assumption is that the scope of GoogleMapsPlugin is greater than the scope of GoogleMapFactory, so it doesn't matter if GoogleMapFactory transitively captures the GooglemapsPlugin instance. If that assumption is violated, all sorts of worse things happen than this particular leak.

I guess my question is what problem are you trying to solve?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct, thanks for pushing back and keeping the code simple!

Copy link
Contributor

@amirh amirh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@amirh
Copy link
Contributor

amirh commented Oct 29, 2020

"Windows Plugins" was fixed and the re-run is green (the presubmit indicator is stale).

@amirh amirh merged commit 8a613b9 into flutter:master Oct 29, 2020
yasargil added a commit to yasargil/plugins that referenced this pull request Oct 30, 2020
* master: (48 commits)
  [video_player] Add toString() to Caption (flutter#3233)
  [google_maps_flutter_web] Show one InfoWindow at a time. (flutter#3224)
  [in_app_purchase] Bump version (flutter#3227)
  [google_maps_flutter] Overhaul lifecycle management in GoogleMapsPlugin (flutter#3213)
  [in_app_purchase] Remove the custom analysis options, fix failing lints. (flutter#3220)
  [video_player]Fixes Playing video from asset on Android (flutter#3123)
  [camera] Added documentation about video not working correctly on Android emulators (flutter#3180)
  Revert "update api"
  update api
  [wifi_info_flutter] Method channel name fixed for android (flutter#3207)
  [share] Fix bug on iPad where `origin` is null and enable XCUITests in the repo (flutter#3210)
  [google_maps_flutter] Clean up google_maps_flutter plugin (flutter#3206)
  Exclude generated_plugin_registrant.cc (flutter#3198)
  broaden the constraint on package:vm_service (flutter#3208)
  Remove unnecessary work around from test in prep for vm_service migration (flutter#3209)
  Add windows directory to examples (flutter#3149)
  [video_player] Upgrade ExoPlayer (flutter#3204)
  [android_alarm_manager] Removed deprecated display1 (flutter#3200)
  [Connectivity] wifi removal (flutter#3173)
  [wifi_info_flutter] make it ready for initial 1.0.0 release  (flutter#3191)
  ...
@slightfoot
Copy link
Member

@math1man
Copy link
Contributor Author

math1man commented Nov 4, 2020

@math1man Is there a reason you didn't use this? https://github.com/flutter/plugins/tree/master/packages/flutter_plugin_android_lifecycle

I do exactly that here.

@slightfoot
Copy link
Member

@math1man Is there a reason you didn't use this? https://github.com/flutter/plugins/tree/master/packages/flutter_plugin_android_lifecycle

I do exactly that here.

My point was your suppose to import that package and use its adapter.

@math1man
Copy link
Contributor Author

math1man commented Nov 5, 2020

Lines 90-93 of GoogleMapsPlugin are this:

  @Override
  public void onAttachedToActivity(ActivityPluginBinding binding) {
    lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding);
  }

Could you elaborate on what you mean?

FlutterSu pushed a commit to FlutterSu/flutter-plugins that referenced this pull request Nov 20, 2020
…in (flutter#3213)

GoogleMapController is now uniformly driven by implementing DefaultLifecycleObserver. That observer is registered to a lifecycle passed via the new LifecycleProvider interface, which has 3 implementations:
1. For v2 plugin registration, GoogleMapsPlugin implements the interface and holds the lifecycle in a field, which is itself controlled by ActivityAware methods.
2. For v1 plugin registration, if the activity implements LifecycleOwner, it's lifecycle is used directly.
3. For v1 plugin registration, if the activity does not implement LifecycleOwner, a proxy lifecycle is created via ActivityLifecycleCallbacks.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
3 participants