packages
packages copied to clipboard
[google_maps_flutter] Fixes exception when dispose is called while asynchronous update from didUpdateWidget is executed
This PR fixes an exception in Google Maps Flutter which occurs when dispose is called while asynchronous code is executed from didUpdateWidget.
https://github.com/flutter/flutter/issues/43785
I've done the work in 2 commits, the first one just used mount checks on after each awaited retrieval of the controller. The second reuses the controller to limit the number of awaited async calls. I am a little unsure of the expected execution order of all the unawaited futures which update the controller in regards to whether dispose could still be called in between their executions. Testing locally I haven't been able to reproduce the exception with a single awaited controller.
I bumped the version 2.12.2 as no feature behaviour has changed. No new tests in this PR, I'm not sure that you can test this change using widget tests. Existing test coverage ensures that the values are still being updated correctly.
Pre-Review Checklist
- [/] I read the Contributor Guide and followed the process outlined there for submitting PRs.
- [/] I read the Tree Hygiene page, which explains my responsibilities.
- [/] I read and followed the relevant style guides and ran the auto-formatter.
- [/] I signed the CLA.
- [/] The title of the PR starts with the name of the package surrounded by square brackets, e.g.
[shared_preferences] - [/] I linked to at least one issue that this PR fixes in the description above.
- [/] I updated
pubspec.yamlwith an appropriate new version according to the pub versioning philosophy, or I have commented below to indicate which version change exemption this PR falls under[^1]. - [/] I updated
CHANGELOG.mdto add a description of the change, following repository CHANGELOG style, or I have commented below to indicate which CHANGELOG exemption this PR falls under[^1]. - [/] I updated/added any relevant documentation (doc comments with
///). - [/] I added new tests to check the change I am making, or I have commented below to indicate which test exemption this PR falls under[^1].
- [/] All existing and new tests are passing.
If you need help, consider asking for advice on the #hackers-new channel on Discord.
[^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
Code Sample
This sample app can be used to trigger the error, just repeatedly tapping the button at the bottom of the screen to reload the map. I did the testing in Chrome using the google_maps_flutter_web example project and the javascript key in web/index.html
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
void main() {
runApp(const MapToggleWidget());
}
class MapToggleWidget extends StatefulWidget {
const MapToggleWidget({Key? key}) : super(key: key);
@override
_MapToggleWidgetState createState() => _MapToggleWidgetState();
}
class _MapToggleWidgetState extends State<MapToggleWidget> {
bool _showMap = true;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Map Toggle Example')),
body: Column(
children: [
Expanded(child: _showMap ? buildGoogleMap() : buildNoMapContainer()),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () => setState(() => _showMap = !_showMap),
child: Text(_showMap ? 'Hide Map' : 'Show Map'),
),
),
],
),
),
);
}
Container buildNoMapContainer() {
return Container(
color: Colors.grey[300],
child: const Center(
child: Text(
'Map is hidden',
style: TextStyle(fontSize: 18),
),
),
);
}
GoogleMap buildGoogleMap() {
return const GoogleMap(
mapType: MapType.satellite,
initialCameraPosition: CameraPosition(
target: LatLng(-38.9000, 175.8000),
zoom: 10.0,
),
);
}
}
It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging.
If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?
Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.
I'm not sure how I would go about writing a test to make sure that the call from async code is not executed if the widget state has been disposed. If it's possible would the execution of the async code being non-deterministic probably result in a flaky test.
If it's possible would the execution of the async code being non-deterministic probably result in a flaky test.
Why would it be non-deterministic in a test? onPlatformViewCreated is public, so you should be able to control when it is called (and thus when awaiting _controller resolves) relative to other methods, by calling it explicitly.
@stuartmorgan-g I've added tests covering both the didUpdateWidget and onPlatformViewCreated calls.
I have also expanded the mount check around the call to onMapCreated inside onPlatformViewCreated as it seems strange to me that you'd want to call it if the widget has been disposed. Do you agree with this or just back it off to only include the call to _updateTileOverlays?
If the mount check around onMapCreated is fine then I'll add a test for it also.
I was also wondering if there is a technical reason the tile overlays are updated only after the controller is available and not set in the initState like the rest of the state?
@tarrinneal Any update on this one? This is causing lot of noise in our bug tracking events manager. Not causing the app to crash, but still very annoying !
From triage: @JamesMcIntosh are you still planning on updating this PR based on the review feedback?
Hi @stuartmorgan-g, Sorry it's been a while, it's on my TODO list for this week. James
I receive error in the web version:
DartError: Bad state: Cannot add new events after calling close
dart-sdk/lib/internal/js_dev_runtime/private/ddc_runtime/errors.dart 288:3 throw
dart-sdk/lib/async/broadcast_stream_controller.dart 243:24 add
packages/google_maps_flutter_web/src/google_maps_controller.dart 250:7
About before comment, I tested in web version that need adaptive this code for google_maps_flutter_web to it didn't show the same error.