Skip to content

[google_maps_flutter] Fixes exception when dispose is called while asynchronous update from didUpdateWidget is executed #9227

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

JamesMcIntosh
Copy link
Contributor

@JamesMcIntosh JamesMcIntosh commented May 8, 2025

This PR fixes an exception in Google Maps Flutter which occurs when dispose is called while asynchronous code is executed from didUpdateWidget.

flutter/flutter#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

If you need help, consider asking for advice on the #hackers-new channel on Discord.

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,
      ),
    );
  }

}

Footnotes

  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. 2 3

@flutter-dashboard
Copy link

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.

@JamesMcIntosh
Copy link
Contributor Author

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.

@stuartmorgan-g
Copy link
Contributor

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.

@JamesMcIntosh JamesMcIntosh force-pushed the RES-1352-mount-checks branch 7 times, most recently from de91b6d to d60e4fc Compare May 10, 2025 11:00
@JamesMcIntosh
Copy link
Contributor Author

@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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants